openapi: 3.1.0
info:
  title: PDFPipe API
  version: "1.0.0"
  description: >
    Turn HTML or a URL into a PDF with one request. Real Chromium rendering,
    flat pricing, and a generous free tier. Authenticate with a Bearer API key.
  contact:
    name: PDFPipe
    url: https://pdfpipe.xyz
    email: hello@pdfpipe.xyz
  license:
    name: Proprietary
servers:
  - url: https://api.pdfpipe.xyz
    description: Production
security:
  - bearerAuth: []
paths:
  /v1/pdf:
    post:
      operationId: generatePdf
      summary: Render a PDF from HTML or a URL
      description: >
        Provide either `html` or `url` plus optional `options`. The response
        body is the raw application/pdf. Usage is returned in the
        X-PDFPipe-Usage, X-PDFPipe-Limit, and X-PDFPipe-Plan response headers.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PdfRequest"
            examples:
              fromHtml:
                value:
                  html: "<h1>Invoice #4012</h1>"
                  options:
                    format: A4
              fromUrl:
                value:
                  url: "https://example.com"
                  options:
                    format: A4
                    landscape: false
      responses:
        "200":
          description: The generated PDF.
          headers:
            X-PDFPipe-Usage:
              schema: { type: integer }
              description: Documents used this month.
            X-PDFPipe-Limit:
              schema: { type: integer }
              description: Monthly document allowance.
            X-PDFPipe-Plan:
              schema: { type: string }
              description: The account plan.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "400":
          description: Invalid request (bad JSON, missing html/url, or a blocked URL).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "401":
          description: Missing or invalid API key.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "402":
          description: Monthly limit reached.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "503":
          description: Renderer at capacity. Safe to retry.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /health:
    get:
      operationId: health
      summary: Liveness check
      security: []
      responses:
        "200":
          description: Service is up.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Your API key, looks like pp_live_...
  schemas:
    PdfRequest:
      type: object
      properties:
        html:
          type: string
          description: HTML to render. Provide this or `url`.
        url:
          type: string
          format: uri
          description: Public URL to render. Provide this or `html`.
        options:
          $ref: "#/components/schemas/PdfOptions"
      oneOf:
        - required: [html]
        - required: [url]
    PdfOptions:
      type: object
      properties:
        format:
          type: string
          enum: [A4, A3, A5, Letter, Legal, Tabloid]
          default: A4
        landscape: { type: boolean, default: false }
        margin: { type: string, default: "1cm", description: Any CSS length. }
        print_background: { type: boolean, default: true }
        scale: { type: number, minimum: 0.1, maximum: 2.0, default: 1.0 }
        page_ranges: { type: string, description: 'For example "1-3, 5".' }
        prefer_css_page_size: { type: boolean, default: false }
        media: { type: string, enum: [print, screen], default: print }
        timeout_ms: { type: integer, minimum: 1000, maximum: 60000, default: 30000 }
        wait_until:
          type: string
          enum: [load, domcontentloaded, networkidle0, networkidle2]
          default: networkidle0
        wait_for: { type: string, description: CSS selector to wait for. }
        wait_ms: { type: integer, minimum: 0, maximum: 5000, default: 0 }
    Error:
      type: object
      properties:
        detail:
          type: string
          description: A human-readable explanation of what went wrong.
