openapi: 3.1.0
info:
  title: My File Tool API
  version: "1.0"
  description: |
    Convert files between formats, merge and edit PDFs, and perform batch operations
    via a simple REST API. Every conversion costs **1 credit**.

    ## Authentication

    All endpoints (except `/formats`, `/tools`, `/register`, and `/verify`) require a
    Bearer token in the `Authorization` header:

    ```
    Authorization: Bearer mt_xxxxxxxxxxxxxxxxxxxx
    ```

    Obtain a key by calling `POST /register`, then verifying your email.

    ## Credits

    New accounts receive **50 free credits**. Purchase more with the `/credits/purchase`
    endpoint. Each file conversion (including each file in a batch) costs 1 credit.

    ## Rate Limits

    Rate-limit headers are included on every response:
    - `X-RateLimit-Limit` -- requests allowed per window
    - `X-RateLimit-Remaining` -- requests remaining
    - `X-RateLimit-Reset` -- Unix timestamp when the window resets

    ## Response Format

    **Success:**
    ```json
    {
      "data": { ... },
      "meta": {
        "request_id": "req_a1b2c3d4e5f6",
        "credits_remaining": 49
      }
    }
    ```

    **Error:**
    ```json
    {
      "error": {
        "code": "error_code",
        "message": "Human-readable message",
        "details": {}
      }
    }
    ```
  contact:
    name: My File Tool Support
    url: https://myfiletool.com/en/contact
  license:
    name: Proprietary

servers:
  - url: https://myfiletool.com/api/v1
    description: Production

security:
  - BearerAuth: []

tags:
  - name: Conversion
    description: One-step and two-step file conversion
  - name: Batch
    description: Batch file operations (convert many files at once)
  - name: PDF
    description: PDF-specific operations (merge, delete pages)
  - name: Formats
    description: Discover supported formats and tools (no auth required)
  - name: Account
    description: Account information and usage statistics
  - name: Registration
    description: Register for an API key and purchase credits

paths:
  # ──────────────────────────────────────────────
  # Conversion
  # ──────────────────────────────────────────────

  /convert:
    post:
      operationId: convertFile
      summary: One-step file conversion
      description: |
        Upload a file and receive the converted result in a single request.

        By default the response body is the **binary converted file**.
        Set `Accept: application/json` to receive a JSON response with metadata
        and a `download_url` instead.
      tags: [Conversion]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, output_format]
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to convert.
                output_format:
                  type: string
                  description: Target format (e.g. `jpeg`, `webp`, `png`, `pdf`).
                  example: webp
                quality:
                  type: integer
                  minimum: 1
                  maximum: 100
                  description: Output quality (1-100). Only applies to lossy formats.
                  example: 85
      responses:
        "200":
          description: Converted file (binary) or JSON metadata.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
            X-Original-Size:
              schema:
                type: integer
              description: Original file size in bytes (binary response only).
            X-Processed-Size:
              schema:
                type: integer
              description: Converted file size in bytes (binary response only).
            X-Processing-Time-Ms:
              schema:
                type: integer
              description: Processing time in milliseconds (binary response only).
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ConversionResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  file_id: "a1b2c3d4e5f6"
                  input_format: heic
                  output_format: jpeg
                  original_size: 2458000
                  processed_size: 890400
                  reduction_percent: 63.8
                  download_url: "/api/v1/files/a1b2c3d4e5f6/download"
                  processing_time_ms: 320
                meta:
                  request_id: "req_a1b2c3d4e5f6"
                  credits_remaining: 49
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/NoCredits"
        "413":
          $ref: "#/components/responses/FileTooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /files/upload:
    post:
      operationId: uploadFile
      summary: Upload a file (two-step conversion, step 1)
      description: |
        Upload a file for later conversion. Returns a `file_id` that you pass to
        `POST /files/{file_id}/convert`.
      tags: [Conversion]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to upload.
      responses:
        "200":
          description: File uploaded successfully.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/UploadResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  file_id: "a1b2c3d4e5f6"
                  extension: heic
                  size: 2458000
                  original_name: "photo.heic"
                meta:
                  request_id: "req_b2c3d4e5f6a1"
                  credits_remaining: 50
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "413":
          $ref: "#/components/responses/FileTooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"

  /files/{file_id}/convert:
    post:
      operationId: convertUploadedFile
      summary: Convert an uploaded file (two-step conversion, step 2)
      description: |
        Convert a previously uploaded file. The request body is JSON (not multipart)
        because the file was already uploaded.
      tags: [Conversion]
      parameters:
        - $ref: "#/components/parameters/FileId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [output_format]
              properties:
                output_format:
                  type: string
                  description: Target format.
                  example: jpeg
                quality:
                  type: integer
                  minimum: 1
                  maximum: 100
                  description: Output quality (1-100). Only applies to lossy formats.
                  example: 85
      responses:
        "200":
          description: Conversion completed.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ConversionResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  file_id: "a1b2c3d4e5f6"
                  input_format: heic
                  output_format: jpeg
                  original_size: 2458000
                  processed_size: 890400
                  reduction_percent: 63.8
                  download_url: "/api/v1/files/a1b2c3d4e5f6/download"
                  processing_time_ms: 280
                  output_extension: jpg
                meta:
                  request_id: "req_c3d4e5f6a1b2"
                  credits_remaining: 49
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/NoCredits"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /files/{file_id}:
    get:
      operationId: getFileStatus
      summary: Check file status
      description: |
        Returns the current status of a file: `pending` (uploaded, not yet converted)
        or `processed` (conversion complete, ready for download).
      tags: [Conversion]
      parameters:
        - $ref: "#/components/parameters/FileId"
      responses:
        "200":
          description: File status.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/FileStatus"
                  meta:
                    $ref: "#/components/schemas/Meta"
              examples:
                processed:
                  summary: File has been converted
                  value:
                    data:
                      file_id: "a1b2c3d4e5f6"
                      status: processed
                      size: 890400
                      download_url: "/api/v1/files/a1b2c3d4e5f6/download"
                    meta:
                      request_id: "req_d4e5f6a1b2c3"
                pending:
                  summary: File is uploaded but not yet converted
                  value:
                    data:
                      file_id: "a1b2c3d4e5f6"
                      status: pending
                      size: 2458000
                    meta:
                      request_id: "req_e5f6a1b2c3d4"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /files/{file_id}/download:
    get:
      operationId: downloadFile
      summary: Download a converted file
      description: |
        Download the processed (converted) file. Returns the binary file content.
        The file must be in `processed` status.
      tags: [Conversion]
      parameters:
        - $ref: "#/components/parameters/FileId"
      responses:
        "200":
          description: The converted file.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  # ──────────────────────────────────────────────
  # Batch
  # ──────────────────────────────────────────────

  /batch:
    post:
      operationId: createBatch
      summary: Create a new batch
      description: |
        Create a batch session for converting multiple files. Creating a batch
        costs **0 credits**; credits are charged when you convert files.
      tags: [Batch]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [output_format]
              properties:
                output_format:
                  type: string
                  description: Target format for all files in the batch.
                  example: webp
            example:
              output_format: webp
      responses:
        "201":
          description: Batch created.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BatchCreated"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  batch_id: "bat_f6a1b2c3d4e5"
                  output_format: webp
                  status: created
                meta:
                  request_id: "req_f6a1b2c3d4e5"
                  credits_remaining: 50
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /batch/{batch_id}/files:
    post:
      operationId: addFileToBatch
      summary: Add a file to a batch
      description: |
        Upload a single file into an existing batch. Repeat this call for each file
        you want to include. The file is stored but **not converted** until you call
        `POST /batch/{batch_id}/convert`.
      tags: [Batch]
      parameters:
        - $ref: "#/components/parameters/BatchId"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to add.
      responses:
        "200":
          description: File added to batch.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BatchFileAdded"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  batch_id: "bat_f6a1b2c3d4e5"
                  file_id: "a1b2c3d4e5f6"
                  extension: png
                  size: 1540000
                  original_name: "banner.png"
                meta:
                  request_id: "req_1a2b3c4d5e6f"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /batch/{batch_id}/convert:
    post:
      operationId: convertBatch
      summary: Convert all files in a batch
      description: |
        Convert all (or a subset of) files in the batch. Each successfully converted
        file costs **1 credit**. The response includes per-file results.
      tags: [Batch]
      parameters:
        - $ref: "#/components/parameters/BatchId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [file_ids, output_format]
              properties:
                file_ids:
                  type: array
                  items:
                    type: string
                  description: Array of file IDs to convert.
                  example: ["a1b2c3d4e5f6", "b2c3d4e5f6a1"]
                output_format:
                  type: string
                  description: Target format.
                  example: webp
                quality:
                  type: integer
                  minimum: 1
                  maximum: 100
                  description: Output quality (1-100).
                  example: 80
      responses:
        "200":
          description: Batch conversion results.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BatchConvertResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  results:
                    - file_id: "a1b2c3d4e5f6"
                      status: success
                      original_size: 1540000
                      processed_size: 312000
                      download_url: "/api/v1/files/a1b2c3d4e5f6/download"
                    - file_id: "b2c3d4e5f6a1"
                      status: success
                      original_size: 2100000
                      processed_size: 445000
                      download_url: "/api/v1/files/b2c3d4e5f6a1/download"
                  total: 2
                  successful: 2
                  failed: 0
                meta:
                  request_id: "req_2b3c4d5e6f1a"
                  credits_remaining: 48
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/NoCredits"
        "429":
          $ref: "#/components/responses/RateLimited"

  /batch/{batch_id}:
    get:
      operationId: getBatchStatus
      summary: Get batch status
      description: Returns the current status and file counts for a batch.
      tags: [Batch]
      parameters:
        - $ref: "#/components/parameters/BatchId"
      responses:
        "200":
          description: Batch status.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/BatchStatus"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  batch_id: "bat_f6a1b2c3d4e5"
                  status: processing
                  file_count: 5
                  processed_count: 3
                  created_at: "2025-06-15 10:30:00"
                meta:
                  request_id: "req_3c4d5e6f1a2b"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  # ──────────────────────────────────────────────
  # PDF
  # ──────────────────────────────────────────────

  /pdf/merge:
    post:
      operationId: mergePdfs
      summary: Merge multiple PDFs
      description: |
        Upload 2-20 PDF files and receive a single merged PDF. Costs **1 credit**.

        Upload files as `files[]` (array) or `file1`, `file2`, etc.

        By default the response body is the **binary merged PDF**. Set
        `Accept: application/json` to receive JSON metadata with a `download_url`.
      tags: [PDF]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  minItems: 2
                  maxItems: 20
                  description: PDF files to merge (2-20 files).
      responses:
        "200":
          description: Merged PDF (binary) or JSON metadata.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PdfMergeResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  file_id: "c3d4e5f6a1b2"
                  input_files: 3
                  output_size: 1250000
                  download_url: "/api/v1/files/c3d4e5f6a1b2/download"
                  processing_time_ms: 450
                meta:
                  request_id: "req_4d5e6f1a2b3c"
                  credits_remaining: 47
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/NoCredits"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  /pdf/delete-pages:
    post:
      operationId: deletePdfPages
      summary: Delete pages from a PDF
      description: |
        Upload a PDF and specify which pages to remove. Costs **1 credit**.

        By default the response body is the **binary PDF** with pages removed. Set
        `Accept: application/json` to receive JSON metadata with a `download_url`.
      tags: [PDF]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, pages]
              properties:
                file:
                  type: string
                  format: binary
                  description: The PDF file.
                pages:
                  type: string
                  description: |
                    Comma-separated page numbers to delete (1-indexed).
                  example: "2,5,8"
      responses:
        "200":
          description: PDF with pages removed (binary) or JSON metadata.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PdfDeletePagesResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  file_id: "d4e5f6a1b2c3"
                  pages_deleted: 3
                  output_size: 980000
                  download_url: "/api/v1/files/d4e5f6a1b2c3/download"
                  processing_time_ms: 210
                meta:
                  request_id: "req_5e6f1a2b3c4d"
                  credits_remaining: 46
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/NoCredits"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

  # ──────────────────────────────────────────────
  # Formats
  # ──────────────────────────────────────────────

  /formats:
    get:
      operationId: listFormats
      summary: List supported conversions
      description: |
        Returns every supported input-to-output format pair. No authentication required.
      tags: [Formats]
      security: []
      responses:
        "200":
          description: Supported conversion formats.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/FormatsResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  conversions:
                    - input_format: heic
                      output_format: jpeg
                    - input_format: heic
                      output_format: png
                    - input_format: png
                      output_format: webp
                    - input_format: jpeg
                      output_format: webp
                  by_input_format:
                    heic: [jpeg, png, webp]
                    jpeg: [png, webp]
                    png: [jpeg, webp]
                  total: 4
                meta:
                  request_id: "req_6f1a2b3c4d5e"

  /tools:
    get:
      operationId: listTools
      summary: List available tools
      description: |
        Returns all conversion tools with their input/output capabilities.
        No authentication required.
      tags: [Formats]
      security: []
      responses:
        "200":
          description: Available tools.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ToolsResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  tools:
                    - id: HeicToJpeg
                      type: convert
                      input_formats: ["image/heic", "image/heif"]
                      output:
                        mime: "image/jpeg"
                        extension: jpg
                    - id: PngToWebp
                      type: convert
                      input_formats: ["image/png"]
                      output:
                        mime: "image/webp"
                        extension: webp
                  total: 2
                meta:
                  request_id: "req_71a2b3c4d5e6"

  # ──────────────────────────────────────────────
  # Account
  # ──────────────────────────────────────────────

  /account:
    get:
      operationId: getAccount
      summary: Get account information
      description: |
        Returns your account details, tier, credit balance, file-size limits, and
        rate-limit configuration.
      tags: [Account]
      responses:
        "200":
          description: Account information.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/AccountInfo"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  name: "My App"
                  email: "dev@example.com"
                  tier: free
                  credits_remaining: 42
                  credits_total_purchased: 0
                  max_file_size: 10485760
                  rate_limits: null
                  created_at: "2025-06-01 08:00:00"
                  last_used_at: "2025-06-15 12:34:56"
                meta:
                  request_id: "req_81a2b3c4d5e6"
                  credits_remaining: 42
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /account/usage:
    get:
      operationId: getUsage
      summary: Get usage statistics
      description: Returns conversion counts and credit usage for a given period.
      tags: [Account]
      parameters:
        - name: period
          in: query
          description: |
            Time period for usage data. Accepts `YYYY-MM-DD` or `YYYY-MM` format. Defaults to the first day of the current month.
          schema:
            type: string
          example: "2025-06-01"
      responses:
        "200":
          description: Usage statistics.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
            X-Credits-Remaining:
              $ref: "#/components/headers/X-Credits-Remaining"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    description: Usage breakdown (structure depends on tracked metrics).
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  period: "2025-06-01"
                  total_conversions: 58
                  total_bytes_uploaded: 152043520
                  total_bytes_downloaded: 48201728
                  daily:
                    - date: "2025-06-15"
                      conversions: 12
                      bytes_uploaded: 31457280
                      bytes_downloaded: 10485760
                    - date: "2025-06-14"
                      conversions: 8
                      bytes_uploaded: 20971520
                      bytes_downloaded: 6291456
                meta:
                  request_id: "req_91a2b3c4d5e6f7a8b9c0d1e2"
                  credits_remaining: 42
        "401":
          $ref: "#/components/responses/Unauthorized"

  # ──────────────────────────────────────────────
  # Registration & Credits
  # ──────────────────────────────────────────────

  /register:
    post:
      operationId: register
      summary: Register for an API key
      description: |
        Submit your email and an application name. A verification email is sent
        with a link that expires in 24 hours. After verifying, you receive your
        API key and **50 free credits**.
      tags: [Registration]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, name]
              properties:
                email:
                  type: string
                  format: email
                  description: Your email address.
                  example: dev@example.com
                name:
                  type: string
                  minLength: 2
                  maxLength: 100
                  description: Application or project name (2-100 characters).
                  example: "My App"
      responses:
        "201":
          description: Registration submitted. Check your email.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/RegisterResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  email: "dev@example.com"
                  verification_sent: true
                  message: "If this email is not already registered, a verification link has been sent."
                meta:
                  request_id: "req_a2b3c4d5e6f7a8b9c0d1e2"
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"

  /verify:
    get:
      operationId: verifyEmail
      summary: Verify email and activate API key
      description: |
        Called via the link in the verification email. Returns the API key in JSON
        (for API clients) or redirects to the developer page (for browsers).

        **Save your API key immediately -- it is shown only once.**
      tags: [Registration]
      security: []
      parameters:
        - name: token
          in: query
          required: true
          description: The 64-character hex verification token from the email link.
          schema:
            type: string
            pattern: "^[a-f0-9]{64}$"
          example: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
      responses:
        "200":
          description: API key activated (JSON response).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/VerifyResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  api_key: "mt_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
                  name: "My App"
                  email: "dev@example.com"
                  message: "API key activated. Save this key -- it will not be shown again."
                meta:
                  request_id: "req_b3c4d5e6f1a2"
        "302":
          description: "Redirect to developer page (browser requests with Accept: text/html)."
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          description: Invalid or expired token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                error:
                  code: not_found
                  message: "Invalid or expired verification token. Please register again."

  /credits/purchase:
    post:
      operationId: purchaseCredits
      summary: Purchase a credit pack
      description: |
        Create a Stripe Checkout session for a credit pack. The response includes a
        `checkout_url` that the user should open in a browser to complete payment.

        | Pack       | Credits | Price  |
        |------------|---------|--------|
        | starter    | 500     | 5 EUR  |
        | pro        | 2,000   | 15 EUR |
        | business   | 10,000  | 50 EUR |
      tags: [Registration]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pack]
              properties:
                pack:
                  type: string
                  enum: [starter, pro, business]
                  description: Credit pack to purchase.
                  example: starter
                language:
                  type: string
                  description: Language code for the checkout page (e.g. `en`, `de`, `fr`).
                  default: en
                  example: en
      responses:
        "200":
          description: Checkout session created.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/X-Request-Id"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/CreditPurchaseResult"
                  meta:
                    $ref: "#/components/schemas/Meta"
              example:
                data:
                  checkout_url: "https://checkout.stripe.com/c/pay/cs_live_xxxxx"
                  session_id: "cs_live_xxxxx"
                  pack: starter
                  credits: 500
                  amount_cents: 500
                  currency: EUR
                meta:
                  request_id: "req_c4d5e6f1a2b3"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"

# ════════════════════════════════════════════════
# Components
# ════════════════════════════════════════════════

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: "mt_xxx"
      description: |
        API key obtained from the registration flow. Pass it as:
        ```
        Authorization: Bearer mt_xxxxxxxxxxxxxxxxxxxx
        ```

  parameters:
    FileId:
      name: file_id
      in: path
      required: true
      description: Unique file identifier returned from an upload or conversion.
      schema:
        type: string
      example: "a1b2c3d4e5f6"

    BatchId:
      name: batch_id
      in: path
      required: true
      description: Unique batch identifier returned from batch creation.
      schema:
        type: string
      example: "bat_f6a1b2c3d4e5"

  headers:
    X-Request-Id:
      description: Unique identifier for this request (format `req_xxxxxxxxxxxx`).
      schema:
        type: string
        example: "req_a1b2c3d4e5f6"

    X-Credits-Remaining:
      description: Number of credits remaining on the authenticated account.
      schema:
        type: integer
        example: 49

    X-RateLimit-Limit:
      description: Maximum number of requests allowed in the current window.
      schema:
        type: integer
        example: 30

    X-RateLimit-Remaining:
      description: Number of requests remaining in the current window.
      schema:
        type: integer
        example: 28

    X-RateLimit-Reset:
      description: Unix timestamp (seconds) when the current rate-limit window resets.
      schema:
        type: integer
        example: 1718450400

  schemas:
    Meta:
      type: object
      properties:
        request_id:
          type: string
          description: Unique request identifier.
          example: "req_a1b2c3d4e5f6"
        credits_remaining:
          type: integer
          description: Credits remaining (present on most authenticated responses).
          example: 49

    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - invalid_api_key
                - key_not_verified
                - rate_limit_exceeded
                - no_credits
                - invalid_file_type
                - file_too_large
                - unsupported_conversion
                - processing_failed
                - invalid_parameter
                - not_found
                - server_error
              description: Machine-readable error code.
            message:
              type: string
              description: Human-readable error message.
            details:
              type: object
              additionalProperties: true
              description: Additional context (varies by error).

    ConversionResult:
      type: object
      properties:
        file_id:
          type: string
          description: Unique file identifier.
        input_format:
          type: string
          description: Detected input format.
        output_format:
          type: string
          description: Requested output format.
        original_size:
          type: integer
          description: Original file size in bytes.
        processed_size:
          type: integer
          description: Converted file size in bytes.
        reduction_percent:
          type: number
          format: float
          description: Size reduction percentage (positive means smaller).
        download_url:
          type: string
          description: Relative URL to download the converted file.
        processing_time_ms:
          type: integer
          description: Processing time in milliseconds.
        output_extension:
          type: string
          description: File extension of the output (present in two-step conversion).

    UploadResult:
      type: object
      properties:
        file_id:
          type: string
          description: Unique file identifier for later conversion.
        extension:
          type: string
          description: Detected file extension.
        size:
          type: integer
          description: Uploaded file size in bytes.
        original_name:
          type: string
          description: Original filename.

    FileStatus:
      type: object
      properties:
        file_id:
          type: string
        status:
          type: string
          enum: [pending, processed]
          description: "`pending` = uploaded, not yet converted. `processed` = ready for download."
        size:
          type: integer
          description: File size in bytes.
        download_url:
          type: string
          description: "Present only when status is `processed`."

    BatchCreated:
      type: object
      properties:
        batch_id:
          type: string
        output_format:
          type: string
        status:
          type: string
          enum: [created]

    BatchFileAdded:
      type: object
      properties:
        batch_id:
          type: string
        file_id:
          type: string
        extension:
          type: string
        size:
          type: integer
        original_name:
          type: string

    BatchConvertResult:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              file_id:
                type: string
              status:
                type: string
                enum: [success, failed]
              original_size:
                type: integer
                description: Present on success.
              processed_size:
                type: integer
                description: Present on success.
              download_url:
                type: string
                description: Present on success.
              error:
                type: string
                description: Present on failure.
        total:
          type: integer
        successful:
          type: integer
        failed:
          type: integer

    BatchStatus:
      type: object
      properties:
        batch_id:
          type: string
        status:
          type: string
          description: "Batch status (e.g. `created`, `processing`, `completed`)."
        file_count:
          type: integer
        processed_count:
          type: integer
        created_at:
          type: string
          format: date-time

    PdfMergeResult:
      type: object
      properties:
        file_id:
          type: string
        input_files:
          type: integer
          description: Number of PDFs that were merged.
        output_size:
          type: integer
        download_url:
          type: string
        processing_time_ms:
          type: integer

    PdfDeletePagesResult:
      type: object
      properties:
        file_id:
          type: string
        pages_deleted:
          type: integer
          description: Number of pages removed.
        output_size:
          type: integer
        download_url:
          type: string
        processing_time_ms:
          type: integer

    FormatsResult:
      type: object
      properties:
        conversions:
          type: array
          items:
            type: object
            properties:
              input_format:
                type: string
              output_format:
                type: string
        by_input_format:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Conversions grouped by input format.
        total:
          type: integer

    ToolsResult:
      type: object
      properties:
        tools:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              type:
                type: string
              input_formats:
                type: array
                items:
                  type: string
              output:
                type: object
                properties:
                  mime:
                    type: string
                  extension:
                    type: string
        total:
          type: integer

    AccountInfo:
      type: object
      properties:
        name:
          type: string
        email:
          type: string
          format: email
        tier:
          type: string
          enum: [free, pro, unlimited]
          description: Account tier.
        credits_remaining:
          type: integer
        credits_total_purchased:
          type: integer
        max_file_size:
          type: integer
          description: Maximum upload size in bytes for this tier.
        rate_limits:
          type: object
          nullable: true
          description: Custom rate limits if applicable, otherwise `null`.
        created_at:
          type: string
          format: date-time
        last_used_at:
          type: string
          format: date-time
          nullable: true

    RegisterResult:
      type: object
      properties:
        email:
          type: string
          format: email
        verification_sent:
          type: boolean
        message:
          type: string

    VerifyResult:
      type: object
      properties:
        api_key:
          type: string
          description: "Your API key (format: `mt_xxx`). Save it -- it is shown only once."
        name:
          type: string
        email:
          type: string
          format: email
        message:
          type: string

    CreditPurchaseResult:
      type: object
      properties:
        checkout_url:
          type: string
          format: uri
          description: Stripe Checkout URL. Open in a browser to complete payment.
        session_id:
          type: string
          description: Stripe session ID.
        pack:
          type: string
          enum: [starter, pro, business]
        credits:
          type: integer
          description: Number of credits in the pack.
        amount_cents:
          type: integer
          description: Price in euro cents.
        currency:
          type: string
          enum: [EUR]

  responses:
    BadRequest:
      description: Invalid request parameters.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: invalid_parameter
              message: "The \"output_format\" parameter is required."

    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: invalid_api_key
              message: "Invalid or missing API key. Provide a valid Bearer token in the Authorization header."

    NoCredits:
      description: No credits remaining.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: no_credits
              message: "No credits remaining. Purchase a credit pack to continue."
              details:
                credits_remaining: 0

    FileTooLarge:
      description: Uploaded file exceeds the size limit.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: file_too_large
              message: "File exceeds the maximum size of 10MB."

    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: not_found
              message: "File not found or expired."

    RateLimited:
      description: Too many requests.
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
        Retry-After:
          schema:
            type: integer
          description: Seconds until the rate-limit window resets.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: rate_limit_exceeded
              message: "Too many requests. Please try again later."
              details:
                retry_after: 42

    ServerError:
      description: Internal server error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: server_error
              message: "An internal error occurred during processing."
