openapi: 3.1.0
info:
  title: PDFjet API
  version: "1.0"
  summary: Turn any PDF into clean data — Markdown, CSV, structured JSON, editable Word, searchable PDF, and schema-guided fields.
  description: |
    REST over HTTPS with Bearer-token auth. Upload a PDF as `multipart/form-data` (field `file`) and
    pick an output. Extractions are deterministic and run on PDFjet's own servers; files are never
    stored. Send an `Idempotency-Key` header on any billable request to make a retry bill at most once.
  contact:
    name: PDFjet
    url: https://pdfjet.dev
    email: support@workollab.com
  license:
    name: Proprietary
servers:
  - url: https://pdfjet.dev
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Extract
    description: Turn a PDF into clean data.
  - name: Convert & utilities
    description: Convert files to PDF; merge, split, encrypt.
  - name: Account
    description: Usage and health.

paths:
  /extract/md:
    post:
      tags: [Extract]
      operationId: extractMarkdown
      summary: PDF → clean, chunk-ready Markdown (RAG-friendly).
      requestBody: { $ref: "#/components/requestBodies/PdfUpload" }
      responses:
        "200":
          description: Markdown text.
          headers:
            X-Pages-Processed: { $ref: "#/components/headers/XPagesProcessed" }
            X-Scanned: { $ref: "#/components/headers/XScanned" }
            X-Amount-Warning: { schema: { type: string }, description: Present if receipt/invoice amounts don't reconcile. }
          content:
            text/markdown: { schema: { type: string } }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }
        "413": { $ref: "#/components/responses/TooLarge" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /extract/csv:
    post:
      tags: [Extract]
      operationId: extractCsv
      summary: Extract the tables in a PDF to CSV.
      requestBody: { $ref: "#/components/requestBodies/PdfUpload" }
      responses:
        "200":
          description: CSV text.
          headers:
            X-Pages-Processed: { $ref: "#/components/headers/XPagesProcessed" }
            X-Tables-Found: { schema: { type: integer }, description: Number of tables detected. }
          content:
            text/csv: { schema: { type: string } }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /extract/json:
    post:
      tags: [Extract]
      operationId: extractJson
      summary: PDF → structured Document Model with bounding-box provenance.
      parameters:
        - name: provenance
          in: query
          required: false
          schema: { type: boolean, default: true }
          description: Include per-element bounding boxes. Set false for a leaner text+structure payload.
      requestBody: { $ref: "#/components/requestBodies/PdfUpload" }
      responses:
        "200":
          description: The structured document.
          headers:
            X-Pages-Processed: { $ref: "#/components/headers/XPagesProcessed" }
            X-Scanned: { $ref: "#/components/headers/XScanned" }
          content:
            application/json: { schema: { $ref: "#/components/schemas/JsonDoc" } }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /extract/fields:
    post:
      tags: [Extract]
      operationId: extractFields
      summary: Schema-guided field extraction (deterministic, on-server).
      description: Send a JSON `schema` describing the fields you want; each comes back typed, with page/bbox provenance and a confidence score.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, schema]
              properties:
                file: { type: string, format: binary }
                schema:
                  type: string
                  description: 'JSON object of field defs, e.g. {"invoice_number":"string","total":"number","due_date":{"type":"string","description":"due date"}}.'
      responses:
        "200":
          description: Extracted fields.
          content:
            application/json: { schema: { $ref: "#/components/schemas/FieldsResult" } }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /extract/chunks:
    post:
      tags: [Extract]
      operationId: extractChunks
      summary: PDF → RAG-ready chunks with provenance and a heading breadcrumb.
      parameters:
        - name: max_tokens
          in: query
          schema: { type: integer, minimum: 64, maximum: 2000, default: 512 }
          description: Target maximum tokens per chunk.
        - name: overlap
          in: query
          schema: { type: integer, minimum: 0, default: 77 }
          description: Approximate token overlap between adjacent chunks (default ~15% of max_tokens).
      requestBody: { $ref: "#/components/requestBodies/PdfUpload" }
      responses:
        "200":
          description: The chunked document.
          content:
            application/json: { schema: { $ref: "#/components/schemas/ChunksResult" } }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /extract/batch:
    post:
      tags: [Extract]
      operationId: extractBatch
      summary: Process several PDFs in one call (per-file results).
      description: Repeat the `file` field. One bad file doesn't fail the batch; results include per-file ok/error. Stops when the account's remaining quota is exhausted.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: array
                  items: { type: string, format: binary }
                  description: One or more PDFs (repeat the `file` field; max 20).
                format:
                  type: string
                  enum: [json, md, csv, chunks]
                  default: json
      responses:
        "200":
          description: Per-file results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  count: { type: integer }
                  format: { type: string }
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        filename: { type: string }
                        ok: { type: boolean }
                        pages: { type: integer }
                        data: { description: The extraction result for the chosen format (present when ok). }
                        error: { $ref: "#/components/schemas/Error" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /extract/docx:
    post:
      tags: [Extract]
      operationId: extractDocx
      summary: Rebuild a PDF as an editable Word (.docx) document.
      requestBody: { $ref: "#/components/requestBodies/PdfUpload" }
      responses:
        "200": { $ref: "#/components/responses/DocxFile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /extract/searchable:
    post:
      tags: [Extract]
      operationId: extractSearchable
      summary: Turn a scanned PDF into a searchable one (OCR text layer; looks identical).
      requestBody: { $ref: "#/components/requestBodies/PdfUpload" }
      responses:
        "200": { $ref: "#/components/responses/PdfFile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /extract/premium:
    post:
      tags: [Extract]
      operationId: extractPremium
      summary: Best-quality Word rebuild for scanned/messy PDFs (AI escalation).
      description: Deterministic-first; escalates to a vision model only when needed. `?engine=off` keeps it fully on-server (Private mode).
      parameters:
        - name: engine
          in: query
          schema: { type: string, enum: [off] }
          description: Set to `off` for Private mode — never sends pages to the third-party model.
      requestBody: { $ref: "#/components/requestBodies/PdfUpload" }
      responses:
        "200":
          description: Editable Word (.docx). Header `X-Engine` reports which engine ran.
          headers:
            X-Engine: { schema: { type: string, enum: [deterministic, ai] } }
          content:
            application/vnd.openxmlformats-officedocument.wordprocessingml.document:
              schema: { type: string, format: binary }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }

  /convert:
    post:
      tags: [Convert & utilities]
      operationId: convertToPdf
      summary: Convert a Word/Excel/PowerPoint/image/HTML file to PDF.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: { type: string, format: binary }
                password: { type: string, description: Optionally encrypt the output PDF. }
                format: { type: string, description: Force an input format (e.g. html). }
      responses:
        "200": { $ref: "#/components/responses/PdfFile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }
        "413": { $ref: "#/components/responses/TooLarge" }

  /merge:
    post:
      tags: [Convert & utilities]
      operationId: mergePdfs
      summary: Merge 2–50 PDFs into one, in order.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: array
                  items: { type: string, format: binary }
                  description: Two or more PDF files (repeat the `file` field).
      responses:
        "200": { $ref: "#/components/responses/PdfFile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /split:
    post:
      tags: [Convert & utilities]
      operationId: splitPdf
      summary: Split a PDF into pages or ranges (one PDF, or a zip of many).
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: { type: string, format: binary }
                pages: { type: string, description: 'Page ranges, e.g. "1-3,5,7-9". Omit to split every page.' }
      responses:
        "200":
          description: One `application/pdf` when a single range, else a `application/zip` of PDFs.
          content:
            application/pdf: { schema: { type: string, format: binary } }
            application/zip: { schema: { type: string, format: binary } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /encrypt:
    post:
      tags: [Convert & utilities]
      operationId: encryptPdf
      summary: Password-encrypt a PDF (AES).
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, password]
              properties:
                file: { type: string, format: binary }
                password: { type: string }
      responses:
        "200": { $ref: "#/components/responses/PdfFile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PageLimit" }

  /usage:
    get:
      tags: [Account]
      operationId: getUsage
      summary: Account page usage, limit, and recent activity for the key.
      description: Read-only; never consumes a page and works even when over the cap.
      responses:
        "200":
          description: Usage summary.
          content:
            application/json: { schema: { $ref: "#/components/schemas/Usage" } }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /healthcheck:
    get:
      tags: [Account]
      operationId: healthcheck
      summary: Liveness + deployed build.
      security: []
      responses:
        "200":
          description: Healthy.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: healthy }
                  version: { type: string }
                  commit: { type: string }
                  builtAt: { type: string, format: date-time, nullable: true }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: pj_live_...
      description: Your API key from https://pdfjet.dev/dashboard.

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema: { type: string }
      description: A client-chosen key; a retry with the same key bills at most once (24h window).

  requestBodies:
    PdfUpload:
      required: true
      content:
        multipart/form-data:
          schema:
            type: object
            required: [file]
            properties:
              file: { type: string, format: binary, description: The PDF to process. }

  headers:
    XPagesProcessed:
      schema: { type: integer }
      description: Pages processed (and billed) for this request.
    XScanned:
      schema: { type: boolean }
      description: Whether the input was detected as a scanned (image-only) PDF.
    RetryAfter:
      schema: { type: integer }
      description: Seconds to wait before retrying (on 429).

  responses:
    PdfFile:
      description: A PDF file.
      headers:
        X-Pages-Processed: { $ref: "#/components/headers/XPagesProcessed" }
      content:
        application/pdf: { schema: { type: string, format: binary } }
    DocxFile:
      description: An editable Word (.docx) file.
      headers:
        X-Pages-Processed: { $ref: "#/components/headers/XPagesProcessed" }
      content:
        application/vnd.openxmlformats-officedocument.wordprocessingml.document:
          schema: { type: string, format: binary }
    InvalidInput:
      description: The request or file was invalid.
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    PageLimit:
      description: Monthly page limit reached.
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    TooLarge:
      description: Request body exceeds the size limit.
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    RateLimited:
      description: Too many requests.
      headers:
        Retry-After: { $ref: "#/components/headers/RetryAfter" }
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }
    ServiceUnavailable:
      description: A transient upstream/processing error — safe to retry.
      content:
        application/json: { schema: { $ref: "#/components/schemas/Error" } }

  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string, example: PAGE_LIMIT_REACHED }
            message: { type: string }
            request_id: { type: string }
    BBox:
      type: object
      properties:
        x: { type: number }
        y: { type: number }
        w: { type: number }
        h: { type: number }
      description: Page-point coordinates, origin top-left.
    Cell:
      type: object
      properties:
        row: { type: integer }
        col: { type: integer }
        text: { type: string }
        bbox: { $ref: "#/components/schemas/BBox" }
    Table:
      type: object
      properties:
        nrows: { type: integer }
        ncols: { type: integer }
        confidence: { type: number, minimum: 0, maximum: 1, description: Rough 0–1 reliability of the reconstructed grid. }
        rows:
          type: array
          items: { type: array, items: { type: string } }
        cells:
          type: array
          items: { $ref: "#/components/schemas/Cell" }
    Block:
      type: object
      properties:
        type: { type: string, enum: [heading, paragraph, list, table] }
        text: { type: string }
        level: { type: integer }
        items: { type: array, items: { type: string } }
        table: { $ref: "#/components/schemas/Table" }
        bbox: { $ref: "#/components/schemas/BBox" }
    JsonPage:
      type: object
      properties:
        number: { type: integer, description: 1-based page number. }
        width: { type: number }
        height: { type: number }
        blocks: { type: array, items: { $ref: "#/components/schemas/Block" } }
    JsonDoc:
      type: object
      properties:
        pageCount: { type: integer }
        scanned: { type: boolean }
        pages: { type: array, items: { $ref: "#/components/schemas/JsonPage" } }
    FieldResult:
      type: object
      properties:
        value:
          description: The extracted value (typed per the schema), or null if not found.
          oneOf: [{ type: string }, { type: number }, { type: boolean }, { type: "null" }]
        page: { type: integer }
        bbox: { $ref: "#/components/schemas/BBox" }
        confidence: { type: number, minimum: 0, maximum: 1 }
    FieldsResult:
      type: object
      properties:
        engine: { type: string, enum: [deterministic] }
        pageCount: { type: integer }
        fields:
          type: object
          additionalProperties: { $ref: "#/components/schemas/FieldResult" }
        warnings:
          type: array
          items: { type: string }
          description: e.g. amounts that don't reconcile.
    Chunk:
      type: object
      properties:
        index: { type: integer }
        text: { type: string }
        tokens: { type: integer }
        headingPath:
          type: array
          items: { type: string }
          description: Section breadcrumb above the chunk.
        pages: { type: array, items: { type: integer } }
        bbox:
          allOf: [{ $ref: "#/components/schemas/BBox" }]
          nullable: true
          description: Highlight region on the chunk's first page; includes a `page` field.
    ChunksResult:
      type: object
      properties:
        pageCount: { type: integer }
        chunkCount: { type: integer }
        maxTokens: { type: integer }
        overlap: { type: integer }
        chunks: { type: array, items: { $ref: "#/components/schemas/Chunk" } }
    UsageEntry:
      type: object
      properties:
        operation: { type: string, enum: [extract, convert, merge, split, encrypt] }
        input_filename: { type: string, nullable: true }
        billed_pages: { type: integer }
        date_created: { type: string, format: date-time }
    Usage:
      type: object
      properties:
        plan: { type: string, example: free }
        limit: { type: integer }
        used: { type: integer, description: Real-time — billed plus in-flight pages. }
        remaining: { type: integer }
        billed: { type: integer, description: Flushed-to-DB total. }
        pending: { type: integer, description: In-flight, not yet flushed. }
        boost_pages: { type: integer }
        resets_at: { type: string, format: date-time }
        recent: { type: array, items: { $ref: "#/components/schemas/UsageEntry" } }
