openapi: 3.1.0
info:
  title: DraftFilter
  version: 1.0.0
servers:
- url: https://api.draftfilter.com
paths:
  /v1/score-jobs:
    get:
      summary: List score jobs
      operationId: listScoreJobs
      parameters:
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          maximum: 100
          minimum: 1
          default: 50
          title: Limit
      - name: cursor
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            minLength: 1
          - type: 'null'
          title: Cursor
      - name: includeResults
        in: query
        required: false
        schema:
          type: boolean
          description: When true, each succeeded job summary includes its full public
            results so a batch can be retrieved in one call.
          default: false
          title: Includeresults
        description: When true, each succeeded job summary includes its full public
          results so a batch can be retrieved in one call.
      responses:
        '200':
          description: Cursor-paginated score job summaries for the authenticated
            API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScoreJobListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
      security: &id001
      - bearerApiKey: []
      - apiKeyHeader: []
      - firebaseIdToken: []
      - firebaseAppCheck: []
    post:
      summary: Create a score job
      operationId: createScoreJob
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScoreJobCreateRequest'
      responses:
        '202':
          description: Score job accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScoreJobCreateResponse'
        '409':
          $ref: '#/components/responses/Conflict'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '422':
          $ref: '#/components/responses/ValidationError'
      security: *id001
  /v1/uploads:
    post:
      summary: Create a file upload
      operationId: createUpload
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadCreateRequest'
      responses:
        '201':
          description: Signed upload URL for one file.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadCreateResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/ContentTooLarge'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security: *id001
  /v1/uploads/{uploadId}/scan:
    post:
      summary: Scan an uploaded file
      operationId: scanUpload
      parameters:
      - name: uploadId
        in: path
        required: true
        schema:
          type: string
          pattern: ^upl_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Uploadid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadScanRequest'
              default:
                forceAsync: false
      responses:
        '200':
          description: Extracted file scan result, or a queued sentence-granularity
            score job when the extracted text exceeds the synchronous scan limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadScanResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/ContentTooLarge'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security: *id001
  /v1/scans:
    post:
      summary: Scan text synchronously
      operationId: createScan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScanCreateRequest'
      responses:
        '200':
          description: Document score, band, confidence, and sentence results in one
            response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScanResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/ContentTooLarge'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security: *id001
  /v1/scans/{scanId}/report:
    get:
      summary: Download a scan report PDF
      operationId: getScanReport
      parameters:
      - name: scanId
        in: path
        required: true
        schema:
          type: string
          pattern: ^scan_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Scanid
      responses:
        '200':
          description: Rendered PDF report.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
      security: *id001
  /v1/shared/{shareId}/report:
    get:
      summary: Download a shared scan report PDF
      operationId: getSharedScanReport
      parameters:
      - name: shareId
        in: path
        required: true
        schema:
          type: string
          pattern: ^share_[0-9a-f]{32,}$
          title: Shareid
      responses:
        '200':
          description: Rendered PDF report.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '404':
          $ref: '#/components/responses/NotFound'
      security: []
  /v1/rewrites/{rewriteId}/report:
    get:
      summary: Download a rewrite report PDF
      operationId: getRewriteReport
      parameters:
      - name: rewriteId
        in: path
        required: true
        schema:
          type: string
          pattern: ^rewrite_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Rewriteid
      responses:
        '200':
          description: Rendered PDF report.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
      security: *id001
  /v1/shared/{shareId}:
    get:
      summary: Get a shared scan
      operationId: getSharedScan
      parameters:
      - name: shareId
        in: path
        required: true
        schema:
          type: string
          pattern: ^share_[0-9a-f]{32,}$
          title: Shareid
      responses:
        '200':
          description: Read-only shared scan result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SharedScanResponse'
        '404':
          $ref: '#/components/responses/NotFound'
      security: []
  /v1/usage:
    get:
      summary: Get account usage
      operationId: getUsage
      responses:
        '200':
          description: Current-period scan/humanize word usage, allowances, and breakdowns
            for the authenticated account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      security: *id001
  /v1/scans/{scanId}/share:
    post:
      summary: Create a scan share link
      operationId: createScanShare
      parameters:
      - name: scanId
        in: path
        required: true
        schema:
          type: string
          pattern: ^scan_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Scanid
      responses:
        '201':
          description: Share link created (or the existing active share returned).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScanShareResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '409':
          $ref: '#/components/responses/Conflict'
      security: *id001
    patch:
      summary: Update a scan share link
      operationId: updateScanShare
      parameters:
      - name: scanId
        in: path
        required: true
        schema:
          type: string
          pattern: ^scan_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Scanid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScanShareUpdateRequest'
      responses:
        '200':
          description: Share link redaction updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScanShareResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '422':
          $ref: '#/components/responses/ValidationError'
      security: *id001
    delete:
      summary: Revoke a scan share link
      operationId: revokeScanShare
      parameters:
      - name: scanId
        in: path
        required: true
        schema:
          type: string
          pattern: ^scan_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Scanid
      responses:
        '204':
          description: Share link revoked.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      security: *id001
  /v1/rewrites:
    post:
      summary: Rewrite text synchronously
      operationId: createRewrite
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RewriteCreateRequest'
      responses:
        '200':
          description: 'Original and rewritten document scores plus the ordered segment
            diff in one response. Anonymous App Check callers receive a redacted once-a-day
            teaser: before/after scores and the single most-improved sentence, never
            the full rewritten text.'
          content:
            application/json:
              schema:
                anyOf:
                - $ref: '#/components/schemas/RewriteResponse'
                - $ref: '#/components/schemas/RewriteTeaserResponse'
                title: Response Createrewrite
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/ContentTooLarge'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
      security: *id001
  /v1/rewrite-jobs:
    post:
      summary: Create a rewrite job
      operationId: createRewriteJob
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RewriteJobCreateRequest'
      responses:
        '202':
          description: Rewrite job accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RewriteJobCreateResponse'
        '409':
          $ref: '#/components/responses/Conflict'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '422':
          $ref: '#/components/responses/ValidationError'
      security: &id002
      - bearerApiKey: []
      - apiKeyHeader: []
      - firebaseIdToken: []
    get:
      summary: List rewrite jobs
      operationId: listRewriteJobs
      parameters:
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          maximum: 100
          minimum: 1
          default: 50
          title: Limit
      - name: cursor
        in: query
        required: false
        schema:
          anyOf:
          - type: string
            minLength: 1
          - type: 'null'
          title: Cursor
      - name: includeResults
        in: query
        required: false
        schema:
          type: boolean
          description: When true, each succeeded job summary includes its full public
            results so a batch can be retrieved in one call.
          default: false
          title: Includeresults
        description: When true, each succeeded job summary includes its full public
          results so a batch can be retrieved in one call.
      responses:
        '200':
          description: Cursor-paginated rewrite job summaries for the authenticated
            API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RewriteJobListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
      security: *id002
  /v1/score-jobs/{jobId}/cancel:
    post:
      summary: Cancel a queued score job
      operationId: cancelScoreJob
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
          pattern: ^job_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Jobid
      responses:
        '200':
          description: Score job cancellation result or current terminal state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScoreJobCancelResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
      security: *id001
  /v1/rewrite-jobs/{jobId}/cancel:
    post:
      summary: Cancel a queued rewrite job
      operationId: cancelRewriteJob
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
          pattern: ^job_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Jobid
      responses:
        '200':
          description: Rewrite job cancellation result or current terminal state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RewriteJobCancelResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
      security: *id002
  /v1/score-jobs/{jobId}:
    get:
      summary: Get a score job
      operationId: getScoreJob
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
          pattern: ^job_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Jobid
      responses:
        '200':
          description: Score job state and public results when complete.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScoreJobResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      security: *id001
  /v1/rewrite-jobs/{jobId}:
    get:
      summary: Get a rewrite job
      operationId: getRewriteJob
      parameters:
      - name: jobId
        in: path
        required: true
        schema:
          type: string
          pattern: ^job_[A-Za-z0-9][A-Za-z0-9_-]*$
          title: Jobid
      responses:
        '200':
          description: Rewrite job state and public results when complete.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RewriteJobResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
      security: *id002
components:
  schemas:
    DashboardRewriteCreditsModel:
      properties:
        usedCredits:
          type: integer
          minimum: 0.0
          title: Usedcredits
        allowanceCredits:
          type: integer
          minimum: 0.0
          title: Allowancecredits
        remainingCredits:
          type: integer
          minimum: 0.0
          title: Remainingcredits
      additionalProperties: false
      type: object
      required:
      - usedCredits
      - allowanceCredits
      - remainingCredits
      title: DashboardRewriteCreditsModel
      description: 'One-time free rewrite credit balance (issue #82); free plan only.'
    ErrorResponse:
      properties:
        error:
          properties:
            code:
              type: string
              title: Code
            message:
              type: string
              title: Message
            pool:
              anyOf:
              - type: string
                enum:
                - scan
                - humanize
              - type: 'null'
              title: Pool
            docs:
              anyOf:
              - type: string
              - type: 'null'
              title: Docs
          additionalProperties: false
          type: object
          required:
          - code
          - message
          title: ErrorResponseDetail
      additionalProperties: false
      type: object
      required:
      - error
      title: ErrorResponse
    PublicSentenceScore:
      properties:
        text:
          type: string
          minLength: 1
          title: Text
        start:
          type: integer
          minimum: 0.0
          title: Start
        end:
          type: integer
          exclusiveMinimum: 0.0
          title: End
        is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Is Ai
        band:
          type: string
          enum:
          - likely_human
          - mixed
          - likely_ai
          title: Band
      additionalProperties: false
      type: object
      required:
      - text
      - start
      - end
      - is_ai
      - band
      title: PublicSentenceScore
    RewriteCreateRequest:
      properties:
        text:
          type: string
          minLength: 1
          title: Text
          description: Single document to rewrite synchronously. Texts over 5000 characters
            are rejected with 413 rewrite_input_too_large; use the asynchronous rewrite
            job API (POST /v1/rewrite-jobs) for longer documents. Anonymous App Check
            callers share the 5000-character anonymous cap and are rejected with 429
            anonymous_input_too_large instead.
        scope:
          type: string
          enum:
          - flagged
          - document
          title: Scope
          description: Use flagged (default) to rewrite paragraphs containing a detector-flagged
            sentence (band != likely_human), or document to rewrite every paragraph.
          default: flagged
        candidateCount:
          type: integer
          maximum: 4.0
          minimum: 1.0
          title: Candidatecount
          description: Number of rewrite candidates to generate per target paragraph
            before selecting the least AI-like option.
          default: 1
      additionalProperties: false
      type: object
      required:
      - text
      title: RewriteCreateRequest
    RewriteJobCancelResponse:
      $ref: '#/components/schemas/RewriteJobResponse'
    RewriteJobCreateRequest:
      properties:
        idempotencyKey:
          anyOf:
          - type: string
            maxLength: 128
            minLength: 1
          - type: 'null'
          title: Idempotencykey
          description: Reusing the same key with the same rewrite request for the
            same API key returns the existing job. Reusing it with a different request
            returns 409.
        candidateCount:
          type: integer
          maximum: 4.0
          minimum: 1.0
          title: Candidatecount
          description: Number of rewrite candidates to generate per target paragraph
            before selecting the least AI-like option.
          default: 1
        scope:
          type: string
          enum:
          - flagged
          - document
          title: Scope
          description: Use flagged (default) to rewrite paragraphs containing a detector-flagged
            sentence (band != likely_human), or document to rewrite every paragraph.
          default: flagged
        items:
          items:
            $ref: '#/components/schemas/RewriteItem'
          type: array
          maxItems: 100
          minItems: 1
          title: Items
          description: Total text across all items must not exceed 100000 characters.
        webhookUrl:
          anyOf:
          - type: string
            maxLength: 2048
            minLength: 1
          - type: 'null'
          title: Webhookurl
          description: Optional completion webhook URL (https required). On terminal
            success or failure the API posts a signed, metadata-only payload to this
            URL; the per-job signing secret is returned once as webhookSecret in this
            response. Delivery configuration is not part of the idempotent request
            identity.
      additionalProperties: false
      type: object
      required:
      - items
      title: RewriteJobCreateRequest
    RewriteJobCreateResponse:
      properties:
        jobId:
          type: string
          title: Jobid
        state:
          type: string
          enum:
          - queued
          - running
          - succeeded
          - failed
          - cancelled
          title: State
        webhookSecret:
          type: string
          title: Webhooksecret
          description: Per-job webhook signing secret; present only when the request
            registered a webhookUrl and shown only in this response.
      additionalProperties: false
      type: object
      required:
      - jobId
      - state
      title: RewriteJobCreateResponse
    RewriteJobListResponse:
      properties:
        items:
          items:
            $ref: '#/components/schemas/RewriteJobSummary'
          type: array
          title: Items
        nextCursor:
          anyOf:
          - type: string
          - type: 'null'
          title: Nextcursor
      additionalProperties: false
      type: object
      required:
      - items
      - nextCursor
      title: RewriteJobListResponse
    RewriteJobResponse:
      properties:
        jobId:
          type: string
          title: Jobid
        state:
          type: string
          enum:
          - queued
          - running
          - succeeded
          - failed
          - cancelled
          title: State
        results:
          items:
            $ref: '#/components/schemas/PublicRewriteResult'
          type: array
          title: Results
        error:
          $ref: '#/components/schemas/ErrorSummary'
          title: Error
      additionalProperties: false
      type: object
      required:
      - jobId
      - state
      title: RewriteJobResponse
    RewriteResponse:
      properties:
        rewriteId:
          type: string
          title: Rewriteid
        segmentsVersion:
          type: integer
          const: 2
          title: Segmentsversion
        original:
          $ref: '#/components/schemas/DocumentScore'
        rewritten:
          $ref: '#/components/schemas/DocumentScore'
        text:
          type: string
          title: Text
          description: The full rewritten document.
        segments:
          items:
            $ref: '#/components/schemas/RewriteSegment'
          type: array
          title: Segments
        iteration:
          type: integer
          maximum: 3.0
          minimum: 1.0
          title: Iteration
        createdAt:
          type: string
          title: Createdat
      additionalProperties: false
      type: object
      required:
      - rewriteId
      - segmentsVersion
      - original
      - rewritten
      - text
      - segments
      - iteration
      - createdAt
      title: RewriteResponse
    RewriteTeaserResponse:
      properties:
        rewriteId:
          type: string
          title: Rewriteid
        teaser:
          type: boolean
          const: true
          title: Teaser
          description: 'Marks a redacted anonymous rewrite preview: the full rewritten
            text is withheld until the caller signs in.'
          default: true
        original:
          $ref: '#/components/schemas/DocumentScore'
        rewritten:
          $ref: '#/components/schemas/DocumentScore'
        sample:
          anyOf:
          - $ref: '#/components/schemas/RewriteTeaserSample'
          - type: 'null'
          description: The single most-improved rewritten paragraph (largest is_ai
            drop), or null when the rewrite changed nothing.
        createdAt:
          type: string
          title: Createdat
      additionalProperties: false
      type: object
      required:
      - rewriteId
      - original
      - rewritten
      - createdAt
      title: RewriteTeaserResponse
      description: 'Server-side redacted rewrite result for anonymous App Check callers.


        The rewrite runs fully, but anonymous responses carry only the

        before/after document scores plus the single most-improved sentence —

        the full rewritten text never leaves the server (issue #69).'
    ScanCreateRequest:
      properties:
        text:
          type: string
          minLength: 1
          title: Text
          description: Single text to scan synchronously. Texts over the plan's per-scan
            character cap (5,000 anonymous, 15,000 free, 50,000 paid) are rejected
            with 413 scan_input_too_large; use the asynchronous score job API for
            longer documents.
      additionalProperties: false
      type: object
      required:
      - text
      title: ScanCreateRequest
    ScanResponse:
      properties:
        scanId:
          type: string
          title: Scanid
        is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Is Ai
        band:
          type: string
          enum:
          - likely_human
          - mixed
          - likely_ai
          title: Band
        confidence:
          type: number
          maximum: 1.0
          minimum: 0.0
          title: Confidence
          description: Document-level confidence from calibration band distance.
        sentences:
          items:
            $ref: '#/components/schemas/PublicSentenceScore'
          type: array
          title: Sentences
        createdAt:
          type: string
          title: Createdat
      additionalProperties: false
      type: object
      required:
      - scanId
      - is_ai
      - band
      - confidence
      - sentences
      - createdAt
      title: ScanResponse
    ScanShareResponse:
      properties:
        share:
          $ref: '#/components/schemas/ScanShare'
      additionalProperties: false
      type: object
      required:
      - share
      title: ScanShareResponse
    ScanShareUpdateRequest:
      properties:
        redactText:
          type: boolean
          title: Redacttext
      additionalProperties: false
      type: object
      required:
      - redactText
      title: ScanShareUpdateRequest
    ScoreJobCancelResponse:
      $ref: '#/components/schemas/ScoreJobResponse'
    ScoreJobCreateRequest:
      properties:
        idempotencyKey:
          anyOf:
          - type: string
            maxLength: 128
            minLength: 1
          - type: 'null'
          title: Idempotencykey
          description: Reusing the same key with the same score request for the same
            API key returns the existing job. Reusing it with a different request
            returns 409.
        granularity:
          type: string
          enum:
          - document
          - sentence
          title: Granularity
          description: Use document for one public score per item, or sentence to
            include sentence spans scored in document context. Sentence granularity
            allows at most 10 items and 50000 total text characters.
          default: document
        items:
          items:
            $ref: '#/components/schemas/ScoreItem'
          type: array
          maxItems: 100
          minItems: 1
          title: Items
          description: Total text across all items must not exceed 100000 characters.
        webhookUrl:
          anyOf:
          - type: string
            maxLength: 2048
            minLength: 1
          - type: 'null'
          title: Webhookurl
          description: Optional completion webhook URL (https required). On terminal
            success or failure the API posts a signed, metadata-only payload to this
            URL; the per-job signing secret is returned once as webhookSecret in this
            response. Delivery configuration is not part of the idempotent request
            identity.
      additionalProperties: false
      type: object
      required:
      - items
      title: ScoreJobCreateRequest
    ScoreJobCreateResponse:
      properties:
        jobId:
          type: string
          title: Jobid
        state:
          type: string
          enum:
          - queued
          - running
          - succeeded
          - failed
          - cancelled
          title: State
        webhookSecret:
          type: string
          title: Webhooksecret
          description: Per-job webhook signing secret; present only when the request
            registered a webhookUrl and shown only in this response.
      additionalProperties: false
      type: object
      required:
      - jobId
      - state
      title: ScoreJobCreateResponse
    ScoreJobListResponse:
      properties:
        items:
          items:
            $ref: '#/components/schemas/ScoreJobSummary'
          type: array
          title: Items
        nextCursor:
          anyOf:
          - type: string
          - type: 'null'
          title: Nextcursor
      additionalProperties: false
      type: object
      required:
      - items
      - nextCursor
      title: ScoreJobListResponse
    ScoreJobResponse:
      properties:
        jobId:
          type: string
          title: Jobid
        state:
          type: string
          enum:
          - queued
          - running
          - succeeded
          - failed
          - cancelled
          title: State
        results:
          items:
            $ref: '#/components/schemas/PublicScoreResult'
          type: array
          title: Results
        error:
          $ref: '#/components/schemas/ErrorSummary'
          title: Error
      additionalProperties: false
      type: object
      required:
      - jobId
      - state
      title: ScoreJobResponse
    SharedScanResponse:
      properties:
        shareId:
          type: string
          title: Shareid
        is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Is Ai
        band:
          type: string
          enum:
          - likely_human
          - mixed
          - likely_ai
          title: Band
        confidence:
          type: number
          maximum: 1.0
          minimum: 0.0
          title: Confidence
          description: Document-level confidence from calibration band distance.
        sentences:
          items:
            $ref: '#/components/schemas/SharedSentenceScore'
          type: array
          title: Sentences
        createdAt:
          type: string
          title: Createdat
      additionalProperties: false
      type: object
      required:
      - shareId
      - is_ai
      - band
      - confidence
      - sentences
      - createdAt
      title: SharedScanResponse
    SharedSentenceScore:
      properties:
        text:
          anyOf:
          - type: string
            minLength: 1
          - type: 'null'
          title: Text
        start:
          type: integer
          minimum: 0.0
          title: Start
        end:
          type: integer
          exclusiveMinimum: 0.0
          title: End
        is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Is Ai
        band:
          type: string
          enum:
          - likely_human
          - mixed
          - likely_ai
          title: Band
      additionalProperties: false
      type: object
      required:
      - start
      - end
      - is_ai
      - band
      title: SharedSentenceScore
    UploadCreateRequest:
      properties:
        filename:
          anyOf:
          - type: string
            maxLength: 255
            minLength: 1
          - type: 'null'
          title: Filename
        contentType:
          type: string
          minLength: 1
          title: Contenttype
          description: 'MIME type of the file to upload. Supported: PDF, DOCX, TXT.'
        sizeBytes:
          type: integer
          minimum: 1.0
          title: Sizebytes
          description: Browser-reported file size. Files over 10485760 bytes return
            413.
      additionalProperties: false
      type: object
      required:
      - contentType
      - sizeBytes
      title: UploadCreateRequest
    UploadCreateResponse:
      properties:
        uploadId:
          type: string
          title: Uploadid
        signedUrl:
          type: string
          title: Signedurl
        expiresAt:
          type: string
          title: Expiresat
      additionalProperties: false
      type: object
      required:
      - uploadId
      - signedUrl
      - expiresAt
      title: UploadCreateResponse
    UploadPageModel:
      properties:
        page:
          type: integer
          minimum: 1.0
          title: Page
        start:
          type: integer
          minimum: 0.0
          title: Start
        end:
          type: integer
          exclusiveMinimum: 0.0
          title: End
      additionalProperties: false
      type: object
      required:
      - page
      - start
      - end
      title: UploadPageModel
    UploadScanRequest:
      properties:
        forceAsync:
          type: boolean
          title: Forceasync
          description: When true, extracted text is always submitted as a sentence-granularity
            score job instead of using the synchronous scan path.
          default: false
      additionalProperties: false
      type: object
      title: UploadScanRequest
    UploadScanResponse:
      properties:
        uploadId:
          type: string
          title: Uploadid
        status:
          type: string
          enum:
          - scanned
          - queued
          title: Status
        pages:
          items:
            $ref: '#/components/schemas/UploadPageModel'
          type: array
          title: Pages
        scanId:
          anyOf:
          - type: string
          - type: 'null'
          title: Scanid
        jobId:
          anyOf:
          - type: string
          - type: 'null'
          title: Jobid
        state:
          anyOf:
          - type: string
            enum:
            - queued
            - running
            - succeeded
            - failed
            - cancelled
          - type: 'null'
          title: State
        is_ai:
          anyOf:
          - type: integer
            maximum: 100.0
            minimum: 0.0
          - type: 'null'
          title: Is Ai
        band:
          anyOf:
          - type: string
            enum:
            - likely_human
            - mixed
            - likely_ai
          - type: 'null'
          title: Band
        confidence:
          anyOf:
          - type: number
            maximum: 1.0
            minimum: 0.0
          - type: 'null'
          title: Confidence
        sentences:
          anyOf:
          - items:
              $ref: '#/components/schemas/PublicSentenceScore'
            type: array
          - type: 'null'
          title: Sentences
        createdAt:
          anyOf:
          - type: string
          - type: 'null'
          title: Createdat
      additionalProperties: false
      type: object
      required:
      - uploadId
      - status
      - pages
      title: UploadScanResponse
    UsageResponse:
      properties:
        accountId:
          type: string
          title: Accountid
        plan:
          type: string
          enum:
          - free
          - plus
          - pro
          - max
          title: Plan
        pools:
          $ref: '#/components/schemas/UsagePools'
        daily:
          items:
            $ref: '#/components/schemas/UsageDaily'
          type: array
          title: Daily
        perKey:
          items:
            $ref: '#/components/schemas/UsageKeyBreakdown'
          type: array
          title: Perkey
        rewriteCredits:
          anyOf:
          - $ref: '#/components/schemas/DashboardRewriteCreditsModel'
          - type: 'null'
      additionalProperties: false
      type: object
      required:
      - accountId
      - plan
      - pools
      - daily
      - perKey
      title: UsageResponse
    ScoreItem:
      properties:
        id:
          type: string
          maxLength: 128
          minLength: 1
          title: Id
        text:
          type: string
          maxLength: 20000
          minLength: 1
          title: Text
      additionalProperties: false
      type: object
      required:
      - id
      - text
      title: ScoreItemRequest
    RewriteItem:
      properties:
        id:
          type: string
          maxLength: 128
          minLength: 1
          title: Id
        text:
          type: string
          maxLength: 5000
          minLength: 1
          title: Text
      additionalProperties: false
      type: object
      required:
      - id
      - text
      title: RewriteItemRequest
    RewriteSegment:
      properties:
        start:
          type: integer
          minimum: 0.0
          title: Start
        end:
          type: integer
          exclusiveMinimum: 0.0
          title: End
        originalText:
          type: string
          minLength: 1
          title: Originaltext
        originalIsAi:
          anyOf:
          - type: integer
            maximum: 100.0
            minimum: 0.0
          - type: 'null'
          title: Originalisai
        rewrittenText:
          type: string
          minLength: 1
          title: Rewrittentext
        rewrittenIsAi:
          anyOf:
          - type: integer
            maximum: 100.0
            minimum: 0.0
          - type: 'null'
          title: Rewrittenisai
        changed:
          type: boolean
          title: Changed
      additionalProperties: false
      type: object
      required:
      - start
      - end
      - originalText
      - rewrittenText
      - changed
      title: RewriteSegmentModel
      description: Ordered document slice in the versioned paragraph-segment contract.
    DocumentScore:
      properties:
        is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Is Ai
        band:
          type: string
          enum:
          - likely_human
          - mixed
          - likely_ai
          title: Band
        confidence:
          type: number
          maximum: 1.0
          minimum: 0.0
          title: Confidence
          description: Document-level confidence from calibration band distance.
      additionalProperties: false
      type: object
      required:
      - is_ai
      - band
      - confidence
      title: DocumentScoreModel
    ScoreJobSummary:
      properties:
        jobId:
          type: string
          title: Jobid
        state:
          type: string
          enum:
          - queued
          - running
          - succeeded
          - failed
          - cancelled
          title: State
        createdAt:
          type: string
          title: Createdat
        updatedAt:
          type: string
          title: Updatedat
        completedAt:
          anyOf:
          - type: string
          - type: 'null'
          title: Completedat
        itemCount:
          type: integer
          minimum: 0.0
          title: Itemcount
        inputBytes:
          type: integer
          minimum: 0.0
          title: Inputbytes
        resultSummary:
          anyOf:
          - $ref: '#/components/schemas/ResultSummary'
          - type: 'null'
        errorSummary:
          anyOf:
          - $ref: '#/components/schemas/ErrorSummary'
          - type: 'null'
        results:
          items:
            $ref: '#/components/schemas/PublicScoreResult'
          type: array
          title: Results
          description: Full public results for succeeded jobs; present only on results-included
            listings (includeResults=true).
      additionalProperties: false
      type: object
      required:
      - jobId
      - state
      - createdAt
      - updatedAt
      - completedAt
      - itemCount
      - inputBytes
      - resultSummary
      - errorSummary
      title: ScoreJobSummaryModel
    RewriteJobSummary:
      properties:
        jobId:
          type: string
          title: Jobid
        state:
          type: string
          enum:
          - queued
          - running
          - succeeded
          - failed
          - cancelled
          title: State
        createdAt:
          type: string
          title: Createdat
        updatedAt:
          type: string
          title: Updatedat
        completedAt:
          anyOf:
          - type: string
          - type: 'null'
          title: Completedat
        itemCount:
          type: integer
          minimum: 0.0
          title: Itemcount
        inputBytes:
          type: integer
          minimum: 0.0
          title: Inputbytes
        resultSummary:
          anyOf:
          - $ref: '#/components/schemas/ResultSummary'
          - type: 'null'
        errorSummary:
          anyOf:
          - $ref: '#/components/schemas/ErrorSummary'
          - type: 'null'
        results:
          items:
            $ref: '#/components/schemas/PublicRewriteResult'
          type: array
          title: Results
          description: Full public results for succeeded jobs; present only on results-included
            listings (includeResults=true).
      additionalProperties: false
      type: object
      required:
      - jobId
      - state
      - createdAt
      - updatedAt
      - completedAt
      - itemCount
      - inputBytes
      - resultSummary
      - errorSummary
      title: RewriteJobSummaryModel
    PublicScoreResult:
      properties:
        id:
          type: string
          title: Id
        is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Is Ai
        band:
          anyOf:
          - type: string
            enum:
            - likely_human
            - mixed
            - likely_ai
          - type: 'null'
          title: Band
        confidence:
          anyOf:
          - type: number
            maximum: 1.0
            minimum: 0.0
          - type: 'null'
          title: Confidence
          description: Document-level confidence from calibration band distance.
        sentences:
          anyOf:
          - items:
              $ref: '#/components/schemas/PublicSentenceScore'
            type: array
          - type: 'null'
          title: Sentences
      additionalProperties: false
      type: object
      required:
      - id
      - is_ai
      title: PublicScoreResultModel
    PublicRewriteResult:
      properties:
        id:
          type: string
          title: Id
        segmentsVersion:
          type: integer
          enum:
          - 1
          - 2
          title: Segmentsversion
        original_is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Original Is Ai
        rewritten_is_ai:
          type: integer
          maximum: 100.0
          minimum: 0.0
          title: Rewritten Is Ai
        rewrittenText:
          type: string
          title: Rewrittentext
        segments:
          anyOf:
          - items:
              $ref: '#/components/schemas/RewriteSegment'
            type: array
          - type: 'null'
          title: Segments
      additionalProperties: false
      type: object
      required:
      - id
      - segmentsVersion
      - original_is_ai
      - rewritten_is_ai
      - rewrittenText
      title: PublicRewriteResultModel
    RewriteTeaserSample:
      properties:
        originalText:
          type: string
          minLength: 1
          title: Originaltext
        originalIsAi:
          anyOf:
          - type: integer
            maximum: 100.0
            minimum: 0.0
          - type: 'null'
          title: Originalisai
        rewrittenText:
          type: string
          minLength: 1
          title: Rewrittentext
        rewrittenIsAi:
          anyOf:
          - type: integer
            maximum: 100.0
            minimum: 0.0
          - type: 'null'
          title: Rewrittenisai
      additionalProperties: false
      type: object
      required:
      - originalText
      - rewrittenText
      title: RewriteTeaserSampleModel
      description: The single most-improved paragraph of an anonymous rewrite teaser.
    ErrorSummary:
      properties:
        code:
          type: string
          title: Code
        message:
          type: string
          title: Message
        retryable:
          type: boolean
          title: Retryable
      additionalProperties: false
      type: object
      required:
      - code
      - message
      - retryable
      title: ErrorSummaryModel
    ResultSummary:
      properties:
        succeededItems:
          type: integer
          minimum: 0.0
          title: Succeededitems
        failedItems:
          type: integer
          minimum: 0.0
          title: Faileditems
      additionalProperties: false
      type: object
      required:
      - succeededItems
      - failedItems
      title: ResultSummaryModel
    UsagePool:
      properties:
        usedWords:
          type: integer
          minimum: 0.0
          title: Usedwords
        allowanceWords:
          type: integer
          minimum: 0.0
          title: Allowancewords
        remainingWords:
          type: integer
          minimum: 0.0
          title: Remainingwords
        periodStart:
          type: string
          title: Periodstart
        periodEnd:
          type: string
          title: Periodend
      additionalProperties: false
      type: object
      required:
      - usedWords
      - allowanceWords
      - remainingWords
      - periodStart
      - periodEnd
      title: UsagePoolModel
    UsagePools:
      properties:
        scan:
          $ref: '#/components/schemas/UsagePool'
        humanize:
          $ref: '#/components/schemas/UsagePool'
      additionalProperties: false
      type: object
      required:
      - scan
      - humanize
      title: UsagePoolsModel
    UsageDaily:
      properties:
        date:
          type: string
          title: Date
        scanWords:
          type: integer
          minimum: 0.0
          title: Scanwords
        humanizeWords:
          type: integer
          minimum: 0.0
          title: Humanizewords
      additionalProperties: false
      type: object
      required:
      - date
      - scanWords
      - humanizeWords
      title: UsageDailyModel
    UsageKeyBreakdown:
      properties:
        apiKeyId:
          anyOf:
          - type: string
          - type: 'null'
          title: Apikeyid
        label:
          type: string
          title: Label
        scanWords:
          type: integer
          minimum: 0.0
          title: Scanwords
        humanizeWords:
          type: integer
          minimum: 0.0
          title: Humanizewords
      additionalProperties: false
      type: object
      required:
      - apiKeyId
      - label
      - scanWords
      - humanizeWords
      title: UsageKeyModel
    ScanShare:
      properties:
        shareId:
          type: string
          title: Shareid
        scanId:
          type: string
          title: Scanid
        redactText:
          type: boolean
          title: Redacttext
        revoked:
          type: boolean
          title: Revoked
        createdAt:
          type: string
          title: Createdat
        updatedAt:
          type: string
          title: Updatedat
      additionalProperties: false
      type: object
      required:
      - shareId
      - scanId
      - redactText
      - revoked
      - createdAt
      - updatedAt
      title: ScanShareModel
  securitySchemes:
    bearerApiKey:
      type: http
      scheme: bearer
      description: 'Customer API key supplied as Authorization: Bearer.'
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-Api-Key
    firebaseIdToken:
      type: http
      scheme: bearer
      description: 'Firebase ID token supplied as Authorization: Bearer.'
    firebaseAppCheck:
      type: apiKey
      in: header
      name: X-Firebase-AppCheck
      description: Firebase App Check token for anonymous browser scans.
  responses:
    BadRequest:
      description: Request could not be accepted.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing or invalid public credential.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: API key does not have the required scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PaymentRequired:
      description: Billing is disabled for the account.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Resource was not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Conflict:
      description: The request conflicts with the current job state or idempotency
        key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ValidationError:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests:
      description: The request exceeded a rate limit.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ContentTooLarge:
      description: The text exceeds the synchronous request limit; use the asynchronous
        job APIs for longer documents.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServiceUnavailable:
      description: The backend is temporarily unavailable; retry shortly.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
security: *id001
