openapi: 3.1.0
info:
  title: proofed.ai API
  version: "1.0.0"
  description: |
    proofed.ai's public HTTP API. Submit documents for AI-powered proofreading
    and receive structured corrections. The Reviewer Feedback (Huxley) endpoints
    require the Huxley permission on your account.

    All requests need a Bearer token in the Authorization header. Generate API
    keys from your Settings page while logged into the proofed.ai web app.
servers:
  - url: "https://proofed.ai/api/v1"
    description: Base URL for this environment
security:
  - bearerAuth: []
tags:
  - name: Documents
    description: Proofreading lifecycle — submit a file, get corrections back.
  - name: Reviewer Feedback
    description: Huxley — rule-grouped feedback on a reviewer's edits. Requires the Huxley permission.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API token created on the Settings page.
  schemas:
    Error:
      type: object
      properties:
        message:
          type: string
          description: Human-readable error message.
        errors:
          type: object
          description: Per-field validation messages (only on 422 responses).
          additionalProperties:
            type: array
            items: { type: string }
    Correction:
      type: object
      description: A single correction in corrections_data.
      properties:
        type:
          type: string
          description: Category of the correction.
          enum: [spelling, grammar, punctuation, style, consistency, clarity, tone, word_choice, formatting, other]
        original:
          type: string
          description: The text as it appears in the document.
        corrected:
          type: string
          description: Suggested replacement. For comments, this is the comment text.
        context:
          type: string
          description: Short rationale explaining the issue.
        page:
          type: integer
          nullable: true
          description: Page number (when known).
    Document:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          description: Stable document identifier.
        filename: { type: string }
        file_type:
          type: string
          description: Lowercase extension (pdf, docx, …).
        file_size:
          type: integer
          description: Size in bytes.
        language: { type: string }
        status:
          type: string
          enum: [pending, processing, completed, failed, cancelled]
        source:
          type: string
          enum: [api, web, email]
        correction_count:
          type: integer
          nullable: true
          description: Number of corrections found. null while pending/processing.
        corrections_data:
          type: array
          description: Full corrections array. Only populated on the Get Document endpoint.
          items: { $ref: "#/components/schemas/Correction" }
        webhook_url:
          type: string
          nullable: true
          format: uri
        submitted_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    Finding:
      type: object
      description: A rule-grouped finding in a reviewer-feedback draft.
      properties:
        rule_slug:
          type: string
          description: Machine-readable rule identifier.
        rule_name:
          type: string
          description: Human-readable rule name (from the style guide if one was provided).
        rule_description:
          type: string
          description: Rule description from the style guide.
        summary:
          type: string
          description: One-sentence feedback the reviewer can send to the editor.
        examples:
          type: array
          description: Concrete examples of the rule in this document.
          items:
            type: object
            properties:
              paragraph: { type: string }
              from: { type: string }
              to: { type: string }
    ReviewerFeedback:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        editor_filename: { type: string }
        reviewer_filename: { type: string }
        language: { type: string }
        status:
          type: string
          enum: [pending, processing, completed, failed, cancelled]
        style_guide_url:
          type: string
          nullable: true
          format: uri
        webhook_url:
          type: string
          nullable: true
          format: uri
        order_id:
          type: string
          nullable: true
          description: Your reference for the order this feedback relates to. Max 255 chars.
        job_id:
          type: string
          nullable: true
          description: Your reference for the job this feedback relates to. Max 255 chars.
        narrative:
          type: string
          nullable: true
          description: Current progress narrative while processing. null once terminal.
        finding_count:
          type: integer
          nullable: true
        diff_count:
          type: integer
          nullable: true
          description: Total number of differences between the editor and reviewer versions.
        summary:
          type: string
          nullable: true
          description: Reviewer-facing summary. Only on the Get endpoint.
        tone_notes:
          type: string
          nullable: true
          description: Reviewer-only tone notes. Only on the Get endpoint.
        findings:
          type: array
          nullable: true
          items: { $ref: "#/components/schemas/Finding" }
          description: Full findings list. Only on the Get endpoint.
        report_available:
          type: boolean
          description: True once the DOCX report can be downloaded.
        submitted_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    DocumentWebhook:
      type: object
      description: Payload POSTed to webhook_url when a document reaches a terminal state.
      properties:
        event:
          type: string
          description: document.{status} — e.g. document.completed, document.failed.
          example: document.completed
        document:
          type: object
          properties:
            uuid: { type: string, format: uuid }
            filename: { type: string }
            status:
              type: string
              enum: [completed, failed]
            correction_count:
              type: integer
              nullable: true
        timestamp:
          type: string
          format: date-time
    ReviewerFeedbackWebhook:
      type: object
      description: Payload POSTed to webhook_url when a reviewer-feedback job reaches a terminal state.
      properties:
        event:
          type: string
          description: reviewer-feedback.{status} — e.g. reviewer-feedback.completed.
          example: reviewer-feedback.completed
        reviewer_feedback:
          type: object
          properties:
            uuid: { type: string, format: uuid }
            editor_filename: { type: string }
            reviewer_filename: { type: string }
            status:
              type: string
              enum: [completed, failed]
            order_id:
              type: string
              nullable: true
            job_id:
              type: string
              nullable: true
            finding_count:
              type: integer
              nullable: true
            diff_count:
              type: integer
              nullable: true
        timestamp:
          type: string
          format: date-time
  responses:
    Unauthorized:
      description: Invalid or missing API token.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    PaymentRequired:
      description: Insufficient tokens — top up to submit more documents.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Forbidden:
      description: You do not have access to this resource.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: The resource does not exist.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    ValidationError:
      description: Validation errors or invalid state transition.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: Too many requests — slow down.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
paths:
  /documents:
    post:
      tags: [Documents]
      summary: Submit a document for proofreading
      description: |
        Upload a file. Consumes one token from your balance. The job runs
        asynchronously — poll Get Document or register a webhook_url to be
        notified when it finishes.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: |
                    The document. Max 50 MB. Supported extensions:
                    pdf, docx, pptx, html, htm, txt, rtf, odt. DOCX files with
                    track changes are accepted — we read the final version.
                    PPTX is ingest-only: findings are returned as JSON, no
                    annotated output file is produced.
                language:
                  type: string
                  enum: [en, en-GB, en-US, es, fr, de, it, pt]
                  default: en
                webhook_url:
                  type: string
                  format: uri
                  description: Optional. POST target for terminal-state notifications.
      responses:
        "201":
          description: Document accepted and queued.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Document" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PaymentRequired" }
        "422": { $ref: "#/components/responses/ValidationError" }
        "429": { $ref: "#/components/responses/RateLimited" }
    get:
      tags: [Documents]
      summary: List documents
      description: Paginated list of the authenticated user's documents, newest first. 20 per page.
      parameters:
        - in: query
          name: page
          schema: { type: integer, minimum: 1, default: 1 }
      responses:
        "200":
          description: Paginated document list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Document" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /documents/{uuid}:
    get:
      tags: [Documents]
      summary: Get a document
      description: Returns full document details, including the corrections_data array once processing is complete.
      parameters:
        - in: path
          name: uuid
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Document details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Document" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /documents/{uuid}/cancel:
    post:
      tags: [Documents]
      summary: Cancel a document
      description: |
        Cancel a pending or processing document. Completed, failed, or
        already-cancelled documents return 422. Cancellation is cooperative —
        in-flight work completes the current step and stops at the next boundary.
      parameters:
        - in: path
          name: uuid
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Document cancelled.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string, example: Document cancelled. }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/ValidationError" }
  /reviewer-feedback:
    post:
      tags: [Reviewer Feedback]
      summary: Submit a reviewer feedback job
      description: |
        Compare an editor's final copy against a reviewer's final copy and
        draft rule-grouped feedback. Requires the Huxley permission.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [editor_file, reviewer_file]
              properties:
                editor_file:
                  type: string
                  format: binary
                  description: |
                    The editor's final copy. Max 50 MB. Supported:
                    pdf, docx, pptx, html, htm, txt, rtf, odt.
                reviewer_file:
                  type: string
                  format: binary
                  description: The reviewer's final copy. Same constraints.
                language:
                  type: string
                  enum: [en, en-GB, en-US, es, fr, de, it, pt]
                  default: en
                style_guide_url:
                  type: string
                  format: uri
                  description: Stylus style-guide URL. Maps reviewer changes to named rules.
                webhook_url:
                  type: string
                  format: uri
                  description: POST target for terminal-state notifications.
                order_id:
                  type: string
                  maxLength: 255
                  description: Your reference for the order. Echoed back in responses and webhooks.
                job_id:
                  type: string
                  maxLength: 255
                  description: Your reference for the job. Echoed back in responses and webhooks.
      responses:
        "201":
          description: Reviewer feedback job accepted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/ReviewerFeedback" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/ValidationError" }
    get:
      tags: [Reviewer Feedback]
      summary: List reviewer-feedback jobs
      description: Paginated list of the authenticated user's reviewer-feedback jobs, newest first. 20 per page.
      parameters:
        - in: query
          name: page
          schema: { type: integer, minimum: 1, default: 1 }
      responses:
        "200":
          description: Paginated list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ReviewerFeedback" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /reviewer-feedback/{uuid}:
    get:
      tags: [Reviewer Feedback]
      summary: Get a reviewer-feedback job
      description: Returns the full feedback draft once complete, or progress narrative while processing.
      parameters:
        - in: path
          name: uuid
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Reviewer-feedback detail.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/ReviewerFeedback" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /reviewer-feedback/{uuid}/cancel:
    post:
      tags: [Reviewer Feedback]
      summary: Cancel a reviewer-feedback job
      description: Cancel a pending or processing job. Terminal jobs return 403.
      parameters:
        - in: path
          name: uuid
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Job cancelled.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string, example: Reviewer feedback cancelled. }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
  /reviewer-feedback/{uuid}/report:
    get:
      tags: [Reviewer Feedback]
      summary: Download the DOCX report
      description: |
        Returns the generated feedback report. Available once status is
        completed — returns 404 otherwise. Filename is derived from the
        reviewer file, suffixed with -huxley-feedback.docx.
      parameters:
        - in: path
          name: uuid
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: DOCX report stream.
          content:
            application/vnd.openxmlformats-officedocument.wordprocessingml.document:
              schema:
                type: string
                format: binary
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
