{
  "openapi": "3.0.3",
  "info": {
    "title": "PodPDF API",
    "version": "1.4.1",
    "description": "Generate PDFs from HTML, Markdown, URLs, images and saved templates, and merge, split,\npassword-protect, unlock or edit existing documents.\n\nThis spec covers the endpoints an **API key** can call. The dashboard-only routes\n(account, billing, key and webhook management, template CRUD) need a Cognito ID token and\nare left out deliberately: an API key is rejected on them at the gateway.\n\nEach successful PDF costs one credit ($0.01), taken from the monthly allowance first, then\nfrom free PDFs (every account starts with 10 on the Free plan), then from credit packs. An\naccount with no plan yet gets `402 UPGRADE_REQUIRED`.\n",
    "contact": {
      "name": "PodPDF support",
      "email": "podpdfapp@gmail.com",
      "url": "https://podpdf.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://podpdf.com/terms/"
    }
  },
  "servers": [
    {
      "url": "https://api.podpdf.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Convert",
      "description": "Turn content into a PDF."
    },
    {
      "name": "Jobs",
      "description": "Check jobs and fetch their output."
    },
    {
      "name": "Templates",
      "description": "Render a saved template."
    },
    {
      "name": "PDF tools",
      "description": "Merge, split, password-protect, unlock and inspect existing PDFs."
    },
    {
      "name": "PDF editor",
      "description": "Stamp, number, annotate and rewrite an uploaded document."
    },
    {
      "name": "Mail merge",
      "description": "Email a personalised PDF to every row of a spreadsheet, as an attachment, through your\nown mail server. Sender connections are added in the dashboard (they hold a password);\neverything else works with an API key.\n"
    },
    {
      "name": "Account",
      "description": "Who the key belongs to, and the public plan list."
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Get the authenticated account",
        "description": "Returns the account behind the key. Useful as a connection test.",
        "responses": {
          "200": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "example": "01KHD59K7TY1EN0JP9BMY7XGHG"
                    },
                    "email": {
                      "type": "string",
                      "format": "email",
                      "example": "someone@example.com"
                    },
                    "name": {
                      "type": "string",
                      "nullable": true,
                      "example": "Jane Doe"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/accounts/me/subscription/plans": {
      "get": {
        "operationId": "listPublicPlans",
        "tags": [
          "Account"
        ],
        "summary": "List the active plans",
        "description": "Public: no credentials needed.",
        "security": [],
        "responses": {
          "200": {
            "description": "The active plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "price_id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "price": {
                            "type": "number"
                          },
                          "monthly_pdfs": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/quickjob": {
      "post": {
        "operationId": "createQuickJob",
        "tags": [
          "Convert"
        ],
        "summary": "Convert to PDF, synchronously",
        "description": "Up to 25 pages, returned within 30 seconds. Send `store: true` for JSON with a signed\ndownload link (valid 1 hour); omit it to get the PDF bytes in the response body.\n\nLarger or slower documents time out with `408 QUICKJOB_TIMEOUT` — use `/longjob`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuickJobRequest"
              },
              "examples": {
                "html": {
                  "summary": "HTML",
                  "value": {
                    "input_type": "html",
                    "html": "<h1>Invoice</h1><p>Thank you.</p>",
                    "options": {
                      "format": "A4",
                      "printBackground": true
                    },
                    "store": true
                  }
                },
                "markdown": {
                  "summary": "Markdown",
                  "value": {
                    "input_type": "markdown",
                    "markdown": "# Report\n\n**Customer:** Acme Ltd",
                    "store": true
                  }
                },
                "url": {
                  "summary": "Public HTTPS URL",
                  "value": {
                    "input_type": "url",
                    "url": "https://example.com/",
                    "store": true
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "input_type",
                  "images"
                ],
                "properties": {
                  "input_type": {
                    "type": "string",
                    "enum": [
                      "image"
                    ]
                  },
                  "images": {
                    "type": "array",
                    "description": "PNG or JPEG files; each becomes one page. Up to 25, 5 MB each.",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON string of options, e.g. `{\"format\":\"A4\",\"fit\":\"contain\"}`."
                  },
                  "store": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The PDF, or JSON when `store` is true",
            "headers": {
              "X-Job-Id": {
                "$ref": "#/components/headers/XJobId"
              },
              "X-PDF-Pages": {
                "$ref": "#/components/headers/XPdfPages"
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoredPdf"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "408": {
            "description": "Rendering took longer than 30 seconds; use `/longjob`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/longjob": {
      "post": {
        "operationId": "createLongJob",
        "tags": [
          "Convert"
        ],
        "summary": "Queue a large document",
        "description": "Asynchronous rendering for HTML or Markdown up to 100 pages. The document is rendered\nonce at submission to count its pages, so this call can take nearly 30 seconds and\nreturns `400 PAGE_LIMIT_EXCEEDED` immediately when the document is too long.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "input_type"
                ],
                "properties": {
                  "input_type": {
                    "type": "string",
                    "enum": [
                      "html",
                      "markdown"
                    ]
                  },
                  "html": {
                    "type": "string"
                  },
                  "markdown": {
                    "type": "string"
                  },
                  "options": {
                    "$ref": "#/components/schemas/PdfOptions"
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Validated but ignored; register webhooks through the dashboard."
                  },
                  "email_notification": {
                    "type": "boolean",
                    "description": "false skips the \"PDF ready\" / \"conversion failed\" email for this job. Omit to follow the account's email settings; true cannot turn it on when job emails are off."
                  }
                }
              },
              "example": {
                "input_type": "markdown",
                "markdown": "# Large report\n\nOne section per page…",
                "options": {
                  "format": "A4"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "$ref": "#/components/schemas/JobId"
                    },
                    "status": {
                      "type": "string",
                      "example": "queued"
                    },
                    "message": {
                      "type": "string"
                    },
                    "estimated_completion": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Always three minutes after submission; a placeholder, not an estimate."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/bulkjob": {
      "post": {
        "operationId": "createBulkJob",
        "tags": [
          "Convert"
        ],
        "summary": "Convert a bundle of files",
        "description": "Converts every file in a ZIP or file list that matches `bundle_type`; the rest are\ntreated as assets for the pages. Send exactly one of `s3_key`, `zip_base64` or\n`files`, or use multipart. Requires `bulk_enabled` on the plan.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bundle_type": {
                    "type": "string",
                    "enum": [
                      "html",
                      "markdown"
                    ],
                    "default": "html"
                  },
                  "s3_key": {
                    "type": "string",
                    "description": "Key from `/bulkjob/upload-url`."
                  },
                  "zip_base64": {
                    "type": "string",
                    "description": "Base64 ZIP, up to 4 MB decoded."
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "path": {
                          "type": "string",
                          "example": "invoice-1.html"
                        },
                        "content_base64": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "options": {
                    "$ref": "#/components/schemas/PdfOptions"
                  },
                  "email_notification": {
                    "type": "boolean",
                    "description": "false skips the \"bulk job finished\" email for this job. Omit to follow the account's email settings; true cannot turn it on when job emails are off."
                  }
                }
              },
              "example": {
                "bundle_type": "html",
                "files": [
                  {
                    "path": "invoice-1.html",
                    "content_base64": "PGgxPkludm9pY2UgMTwvaDE+"
                  }
                ]
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "bundle_type": {
                    "type": "string",
                    "enum": [
                      "html",
                      "markdown"
                    ]
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  },
                  "paths": {
                    "type": "string",
                    "description": "JSON array of relative paths, in the same order as the files."
                  },
                  "options": {
                    "type": "string"
                  },
                  "email_notification": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ],
                    "description": "\"false\" skips the \"bulk job finished\" email for this job."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "$ref": "#/components/schemas/JobId"
                    },
                    "status": {
                      "type": "string",
                      "example": "queued"
                    },
                    "job_type": {
                      "type": "string",
                      "example": "bulk"
                    },
                    "bundle_type": {
                      "type": "string",
                      "example": "html"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bulkjob/upload-url": {
      "post": {
        "operationId": "createBulkUploadUrl",
        "tags": [
          "Convert"
        ],
        "summary": "Get a signed URL for a large ZIP",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content_length_bytes"
                ],
                "properties": {
                  "content_length_bytes": {
                    "type": "integer",
                    "example": 15728640
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where to PUT the ZIP",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "upload_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "s3_key": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "max_bytes": {
                      "type": "integer"
                    },
                    "required_headers": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/jobs/{job_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobIdPath"
        }
      ],
      "get": {
        "operationId": "getJob",
        "tags": [
          "Jobs"
        ],
        "summary": "Get a job",
        "responses": {
          "200": {
            "description": "The job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/jobs/{job_id}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobIdPath"
        }
      ],
      "get": {
        "operationId": "getJobDownloadLink",
        "tags": [
          "Jobs"
        ],
        "summary": "Get a fresh download link",
        "description": "Works for any job with stored output: long, bulk, merge and split jobs, quick or\ntemplate jobs sent with `store: true`, and finished mail merge campaigns (the report CSV).\nLinks last one hour; files are kept 30 days.\n",
        "responses": {
          "200": {
            "description": "A signed link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "$ref": "#/components/schemas/JobId"
                    },
                    "download_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_in_seconds": {
                      "type": "integer",
                      "example": 3600
                    },
                    "content_type": {
                      "type": "string",
                      "example": "application/pdf"
                    },
                    "size_bytes": {
                      "type": "integer",
                      "example": 1843200
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The job has not finished yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          }
        }
      }
    },
    "/jobs/{job_id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobIdPath"
        }
      ],
      "post": {
        "operationId": "cancelBulkJob",
        "tags": [
          "Jobs"
        ],
        "summary": "Cancel a bulk job or mail merge campaign",
        "description": "Bulk jobs and mail merge campaigns. A campaign that is sending stops within a few emails\n(202): emails already sent are charged and the rest are marked `skipped` with\n`error_code: CANCELLED`; a `mailmerge.campaign.cancelled` webhook follows.\n\nA queued job is cancelled at once and nothing is charged (200). A bulk job that\nis converting stops before its next file (202): the PDFs it already made stay in its\noutput, downloadable from `/jobs/{job_id}/download`, and are charged; the rest are marked\n`skipped` with `error_code: CANCELLED`. Keep polling `GET /jobs/{job_id}` until `status` is\n`cancelled`. A `bulk.job.cancelled` webhook is sent either way.\n",
        "responses": {
          "200": {
            "description": "The queued job was cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelJobResult"
                }
              }
            }
          },
          "202": {
            "description": "The job stops before its next file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelJobResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The job has already finished, is a draft, or cannot be cancelled (`JOB_NOT_CANCELLABLE`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{job_id}/files": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobIdPath"
        }
      ],
      "get": {
        "operationId": "listJobFiles",
        "tags": [
          "Jobs"
        ],
        "summary": "List the files a bulk job produced",
        "responses": {
          "200": {
            "description": "One entry per file in the bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "$ref": "#/components/schemas/JobId"
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "index": {
                            "type": "integer"
                          },
                          "path": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "success",
                              "failed",
                              "skipped"
                            ]
                          },
                          "pages": {
                            "type": "integer",
                            "nullable": true
                          },
                          "error_message": {
                            "type": "string",
                            "nullable": true
                          },
                          "blocked_requests": {
                            "type": "integer"
                          },
                          "missing_assets": {
                            "type": "integer"
                          },
                          "render_timeout": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/jobs/{job_id}/files/{file_index}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/JobIdPath"
        },
        {
          "name": "file_index",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          },
          "description": "Index from `GET /jobs/{job_id}/files`."
        }
      ],
      "get": {
        "operationId": "getJobFileDownloadLink",
        "tags": [
          "Jobs"
        ],
        "summary": "Get a download link for one file of a bulk job",
        "responses": {
          "200": {
            "description": "A signed link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "download_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          }
        }
      }
    },
    "/templates/{template_id}/render": {
      "parameters": [
        {
          "name": "template_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "From the dashboard, under Templates → Use via API."
        }
      ],
      "post": {
        "operationId": "renderTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Render a saved template",
        "description": "Send the field values only; layout, totals, currency formatting and page numbering come\nfrom the template. Nested fields are nested objects, and list fields are arrays of\nobjects. Up to 1 MB of data.\n\nTemplates written in your own HTML (beta) render through this same operation. They print\nthe values they are given and do not calculate totals.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "filename": {
                    "type": "string"
                  },
                  "store": {
                    "type": "boolean",
                    "default": false
                  }
                }
              },
              "example": {
                "data": {
                  "invoice_number": "INV-1042",
                  "customer": {
                    "name": "Acme Ltd",
                    "address": "1 Main St"
                  },
                  "items": [
                    {
                      "description": "Design work",
                      "quantity": 10,
                      "unit_price": 85
                    }
                  ],
                  "tax_rate": 20
                },
                "store": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The PDF, or JSON when `store` is true",
            "headers": {
              "X-Job-Id": {
                "$ref": "#/components/headers/XJobId"
              },
              "X-PDF-Pages": {
                "$ref": "#/components/headers/XPdfPages"
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/StoredPdf"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "template_id": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/pdf/merge": {
      "post": {
        "operationId": "mergePdfs",
        "tags": [
          "PDF tools"
        ],
        "summary": "Merge PDFs",
        "description": "One `files` part per PDF, in merge order. Up to 4 MB in total; larger sets go through `/pdf/upload-url` and `/pdf/jobs`.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "files"
                ],
                "properties": {
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  },
                  "page_ranges": {
                    "type": "string",
                    "description": "JSON array with one entry per file — a page range, or null for every page."
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON object; `output_filename`, `title`."
                  },
                  "store": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merged PDF, or JSON when `store` is true or the result is over 4.4 MB",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PdfOpResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          }
        }
      }
    },
    "/pdf/split": {
      "post": {
        "operationId": "splitPdf",
        "tags": [
          "PDF tools"
        ],
        "summary": "Split a PDF",
        "description": "Modes are `extract`, `ranges`, `every_n` and `each_page`. A split that makes one file\nreturns the PDF. A split that makes several stores them and returns JSON with a link for\neach file and `zip_url`, a link to a ZIP of them all.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "split"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "split": {
                    "type": "string",
                    "description": "JSON object, e.g. `{\"mode\":\"extract\",\"pages\":\"1-3\"}`."
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON object; `filename_pattern`."
                  },
                  "store": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The PDF when there is one output, or JSON when there are several, `store` is true or the PDF is over 4.4 MB",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PdfOpResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          }
        }
      }
    },
    "/pdf/protect": {
      "post": {
        "operationId": "protectPdf",
        "tags": [
          "PDF tools"
        ],
        "summary": "Password-protect a PDF",
        "description": "Returns a copy of one PDF encrypted with AES-256. Set `user_password` (needed to open\nthe PDF), `owner_password` with `permissions` (restrict printing, copying or editing), or\nboth.\n\nSend the PDF in the request (up to 4 MB), or upload it with `/pdf/upload-url` and pass\nits key as `source_key` (up to 150 MB and 5000 pages). Either way it is one request:\nthere is no background job. Passwords are used to write the file and are never stored,\nlogged or returned.\n\nA PDF that is already protected is refused with `422 PDF_ENCRYPTED`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "user_password": {
                    "type": "string",
                    "format": "password",
                    "description": "Needed to open the PDF. Give this, `owner_password`, or both."
                  },
                  "owner_password": {
                    "type": "string",
                    "format": "password",
                    "description": "Needed to change or remove the protection. Required when `permissions` restricts anything; must differ from `user_password`."
                  },
                  "permissions": {
                    "type": "string",
                    "description": "JSON object, e.g. `{\"print\":\"none\",\"copy\":false}`. See `ProtectPermissions`."
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON object; `output_filename`."
                  },
                  "store": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ]
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProtectRequest"
              },
              "examples": {
                "openPassword": {
                  "summary": "A password to open",
                  "value": {
                    "input": {
                      "pdf_base64": "JVBERi0xLjcK...",
                      "filename": "payslip.pdf"
                    },
                    "user_password": "correct-horse-battery"
                  }
                },
                "restricted": {
                  "summary": "An uploaded file, no printing or copying",
                  "value": {
                    "source_key": "pdf-uploads/01HZ…/01J8…pdf",
                    "filename": "board-pack.pdf",
                    "user_password": "correct-horse-battery",
                    "owner_password": "a-different-one",
                    "permissions": {
                      "print": "none",
                      "copy": false
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The protected PDF, or JSON when `store` is true or the PDF was given by `source_key`",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PdfSecurityResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "408": {
            "$ref": "#/components/responses/PdfOpTimeout"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/PdfUnprocessable"
          }
        }
      }
    },
    "/pdf/unlock": {
      "post": {
        "operationId": "unlockPdf",
        "tags": [
          "PDF tools"
        ],
        "summary": "Unlock a password-protected PDF",
        "description": "Returns a copy of a protected PDF with no password and no restrictions, which the merge,\nsplit and edit endpoints accept.\n\nA correct password is always required. A PDF with a password to open is unlocked by\nthat password or by its owner password. A PDF that opens without a password but\nrestricts printing, copying or editing needs its owner password.\n\nSend the PDF in the request (up to 4 MB) or by `source_key` (up to 150 MB and 5000\npages). A wrong password is `422 PDF_PASSWORD_INCORRECT` and is not charged.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "password"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "password": {
                    "type": "string",
                    "format": "password"
                  },
                  "options": {
                    "type": "string",
                    "description": "JSON object; `output_filename`."
                  },
                  "store": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ]
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnlockRequest"
              },
              "example": {
                "input": {
                  "pdf_base64": "JVBERi0xLjcK...",
                  "filename": "statement.pdf"
                },
                "password": "correct-horse-battery"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The unlocked PDF, or JSON when `store` is true or the PDF was given by `source_key`",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PdfSecurityResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "408": {
            "$ref": "#/components/responses/PdfOpTimeout"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/PdfUnprocessable"
          }
        }
      }
    },
    "/pdf/inspect": {
      "post": {
        "operationId": "inspectPdf",
        "tags": [
          "PDF tools"
        ],
        "summary": "Inspect a PDF",
        "description": "Page count, page sizes and whether the file is encrypted or carries forms. Free — no credit is spent.\n\nWith `for: edit` it also returns what an editor recipe needs: a stable id for every page\n(`p0`, `p1`, …) with its size and rotation, and the document's form fields. That mode\nrefuses documents over 200 pages and needs a plan that includes the editor.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "s3_key": {
                    "type": "string",
                    "description": "Key from `/pdf/upload-url` (up to 150 MB)."
                  },
                  "filename": {
                    "type": "string"
                  },
                  "input": {
                    "$ref": "#/components/schemas/InlinePdf"
                  },
                  "for": {
                    "type": "string",
                    "enum": [
                      "edit"
                    ],
                    "description": "Add page ids, geometry and form fields for the editor."
                  }
                }
              },
              "example": {
                "s3_key": "pdf-uploads/01HZ…/01J8…pdf",
                "for": "edit"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Up to 4 MB."
                  },
                  "for": {
                    "type": "string",
                    "enum": [
                      "edit"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the file contains",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InspectResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/pdf/upload-url": {
      "post": {
        "operationId": "createPdfUploadUrl",
        "tags": [
          "PDF tools"
        ],
        "summary": "Get a signed URL for a large PDF",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content_length_bytes"
                ],
                "properties": {
                  "content_length_bytes": {
                    "type": "integer",
                    "example": 482913
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where to PUT the file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "upload_url": {
                      "type": "string",
                      "format": "uri",
                      "description": "PUT the PDF here, with the headers in `required_headers` and no API key."
                    },
                    "s3_key": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "max_bytes": {
                      "type": "integer",
                      "description": "Largest PDF this upload accepts, in bytes.",
                      "example": 157286400
                    },
                    "required_headers": {
                      "type": "object",
                      "description": "Headers the PUT must send exactly, or the signature is rejected.",
                      "additionalProperties": {
                        "type": "string"
                      },
                      "example": {
                        "Content-Type": "application/pdf"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/pdf/jobs": {
      "post": {
        "operationId": "createPdfToolJob",
        "tags": [
          "PDF tools"
        ],
        "summary": "Merge or split uploaded PDFs as a job",
        "description": "For sets over 4 MB, uploaded with `/pdf/upload-url` first.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "operation",
                  "inputs"
                ],
                "properties": {
                  "operation": {
                    "type": "string",
                    "enum": [
                      "merge",
                      "split"
                    ]
                  },
                  "inputs": {
                    "type": "array",
                    "description": "2 to 100 entries for merge, exactly 1 for split.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "s3_key": {
                          "type": "string"
                        },
                        "filename": {
                          "type": "string"
                        },
                        "pages": {
                          "type": "string",
                          "description": "Page range; merge only."
                        }
                      }
                    }
                  },
                  "split": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "options": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              },
              "example": {
                "operation": "split",
                "inputs": [
                  {
                    "s3_key": "pdf-uploads/01HZ…/01J8…pdf",
                    "filename": "payslips.pdf"
                  }
                ],
                "split": {
                  "mode": "each_page"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "$ref": "#/components/schemas/JobId"
                    },
                    "status": {
                      "type": "string",
                      "example": "queued"
                    },
                    "operation": {
                      "type": "string",
                      "example": "split"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/pdf/edit/preflight": {
      "post": {
        "operationId": "preflightPdfEdit",
        "tags": [
          "PDF editor"
        ],
        "summary": "See what a recipe would do",
        "description": "Runs the recipe against the document without writing anything and reports, for each\noperation, the pages it resolves to and what it would do — or why it cannot. Free, and\nnever recorded as a job. A bad recipe still returns `200` with `ok: false`; the failing\noperation is marked `blocking` and everything after it `not_evaluated`.\n",
        "requestBody": {
          "$ref": "#/components/requestBodies/EditRequest"
        },
        "responses": {
          "200": {
            "description": "What the recipe would do",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EditPreflightResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/pdf/edit": {
      "post": {
        "operationId": "editPdf",
        "tags": [
          "PDF editor"
        ],
        "summary": "Apply a recipe to a document",
        "description": "Applies the operations in order and stores the new document, returning a download link\nvalid for one hour (the file is kept for 7 days). All or nothing: if any operation fails,\nnothing is written and nothing is charged. One charge per save, however many operations\nit carries. Saving rewrites the whole file, so an existing digital signature stops\nvalidating — preflight reports that as `signatures_invalidated`.\n\nSend a `commit_token` to make retries safe: repeating a save with the same token returns\nthe first result (`repeated: true`) instead of charging again.\n",
        "requestBody": {
          "$ref": "#/components/requestBodies/EditRequest"
        },
        "responses": {
          "200": {
            "description": "The edited document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EditResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "408": {
            "description": "`PDFOP_TIMEOUT` — the save took longer than 25 seconds. Nothing is written or charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`COMMIT_IN_PROGRESS` — a save with this `commit_token` is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/mailmerge/connections": {
      "get": {
        "operationId": "listMailConnections",
        "tags": [
          "Mail merge"
        ],
        "summary": "List sender connections",
        "description": "The mail servers campaigns can send through. Connections are created, changed and tested\nin the dashboard; the password is never returned.\n",
        "responses": {
          "200": {
            "description": "The account's connections",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MailConnection"
                      }
                    },
                    "count": {
                      "type": "integer",
                      "example": 1
                    },
                    "max_connections": {
                      "type": "integer",
                      "example": 3
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/mailmerge/connections/{connection_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ConnectionIdPath"
        }
      ],
      "get": {
        "operationId": "getMailConnection",
        "tags": [
          "Mail merge"
        ],
        "summary": "Get a sender connection",
        "responses": {
          "200": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connection": {
                      "$ref": "#/components/schemas/MailConnection"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/mailmerge/uploads": {
      "post": {
        "operationId": "createMailMergeUploadUrl",
        "tags": [
          "Mail merge"
        ],
        "summary": "Get a signed URL for a recipient list",
        "description": "PUT a CSV or JSON list (up to 10 MB) to the URL, then pass `s3_key` as `input.s3_key`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content_length_bytes"
                ],
                "properties": {
                  "content_length_bytes": {
                    "type": "integer",
                    "example": 48213
                  },
                  "content_type": {
                    "type": "string",
                    "enum": [
                      "text/csv",
                      "application/json"
                    ],
                    "default": "text/csv"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where to PUT the list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "upload_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "s3_key": {
                      "type": "string",
                      "example": "mailmerge-uploads/01HZXUSERID/01J9UPLOAD.csv"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "max_bytes": {
                      "type": "integer",
                      "example": 10485760
                    },
                    "required_headers": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          }
        }
      }
    },
    "/mailmerge/limit-request": {
      "post": {
        "operationId": "requestMailMergeLimit",
        "tags": [
          "Mail merge"
        ],
        "summary": "Ask for more recipients per campaign",
        "description": "Campaigns send up to 100 recipients each. Ask for up to 10,000 with the number you need\nand what you will send; requests are reviewed within one business day. One request a day.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "requested_rows",
                  "use_case"
                ],
                "properties": {
                  "requested_rows": {
                    "type": "integer",
                    "minimum": 2,
                    "maximum": 10000,
                    "example": 2500
                  },
                  "use_case": {
                    "type": "string",
                    "minLength": 30,
                    "maxLength": 2000,
                    "example": "Monthly statements to our 2,400 business customers, who asked to receive them by email."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Request received",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "requested_rows": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "example": "pending"
                    },
                    "requested_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "description": "`LIMIT_REQUEST_PENDING` — you asked in the last 24 hours",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mailmerge/campaigns": {
      "get": {
        "operationId": "listMailMergeCampaigns",
        "tags": [
          "Mail merge"
        ],
        "summary": "List campaigns",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "queued",
                "processing",
                "completed",
                "partial_failed",
                "failed",
                "cancelled"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "next_token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaigns": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MailMergeCampaign"
                      }
                    },
                    "next_token": {
                      "type": "string",
                      "nullable": true
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createMailMergeCampaign",
        "tags": [
          "Mail merge"
        ],
        "summary": "Create a campaign",
        "description": "Parses the recipient list, maps its columns to the template's fields and validates every\nrow. Returns a draft with a validation report (201), or with `start: true` and consent,\nqueues it straight away (202). Each email the mail server accepts costs one PDF credit.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMailMergeCampaign"
              },
              "example": {
                "name": "October statements",
                "template_id": "01J8TEMPLATE0000000000000",
                "connection_id": "01J9A1B2C3D4E5F6G7H8J9K0LM",
                "input": {
                  "rows": [
                    {
                      "Email": "ann@example.com",
                      "Name": "Ann Lee",
                      "Amount": "1204.50"
                    },
                    {
                      "Email": "bo@example.com",
                      "Name": "Bo Chan",
                      "Amount": "88.00"
                    }
                  ]
                },
                "mapping": {
                  "customer.name": "Name",
                  "amount": "Amount"
                },
                "email": {
                  "subject": "Your statement, {{customer.name}}",
                  "body_text": "Hello {{customer.name}},\nYour statement for {{amount}} is attached.",
                  "attachment_filename": "Statement {{customer.name}}"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign": {
                      "$ref": "#/components/schemas/MailMergeCampaign"
                    },
                    "validation": {
                      "$ref": "#/components/schemas/MailMergeValidation"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Created and queued (`start: true`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign": {
                      "$ref": "#/components/schemas/MailMergeCampaign"
                    },
                    "validation": {
                      "$ref": "#/components/schemas/MailMergeValidation"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`CONNECTION_NOT_VERIFIED` or `MAILMERGE_CAMPAIGN_ALREADY_ACTIVE` when starting",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          }
        }
      }
    },
    "/mailmerge/campaigns/{campaign_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CampaignIdPath"
        }
      ],
      "get": {
        "operationId": "getMailMergeCampaign",
        "tags": [
          "Mail merge"
        ],
        "summary": "Get a campaign",
        "responses": {
          "200": {
            "description": "The campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign": {
                      "$ref": "#/components/schemas/MailMergeCampaign"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "updateMailMergeCampaign",
        "tags": [
          "Mail merge"
        ],
        "summary": "Change a draft",
        "description": "Changing the mapping, email column or options re-validates every row.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "connection_id": {
                    "type": "string"
                  },
                  "email_column": {
                    "type": "string"
                  },
                  "mapping": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "email": {
                    "$ref": "#/components/schemas/MailMergeEmail"
                  },
                  "options": {
                    "$ref": "#/components/schemas/MailMergeOptions"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign": {
                      "$ref": "#/components/schemas/MailMergeCampaign"
                    },
                    "validation": {
                      "$ref": "#/components/schemas/MailMergeValidation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`MAILMERGE_NOT_DRAFT` — the campaign has started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          }
        }
      },
      "delete": {
        "operationId": "deleteMailMergeCampaign",
        "tags": [
          "Mail merge"
        ],
        "summary": "Delete a draft",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`MAILMERGE_NOT_DRAFT` — the campaign has started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mailmerge/campaigns/{campaign_id}/preview": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CampaignIdPath"
        }
      ],
      "post": {
        "operationId": "previewMailMergeRow",
        "tags": [
          "Mail merge"
        ],
        "summary": "Preview one row",
        "description": "The email and the template HTML for one row, exactly as they will be sent. Free; nothing is sent.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "row_index": {
                    "type": "integer",
                    "description": "0 is the first data row. Defaults to the first valid row."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The preview",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MailMergePreview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`MAILMERGE_NOT_DRAFT`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          }
        }
      }
    },
    "/mailmerge/campaigns/{campaign_id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CampaignIdPath"
        }
      ],
      "post": {
        "operationId": "sendMailMergeTest",
        "tags": [
          "Mail merge"
        ],
        "summary": "Send one row to yourself",
        "description": "Renders one row's real PDF and sends it to `to` (default: the account email) with\n`[Test]` before the subject. Charged as one PDF; five per campaign.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "row_index": {
                    "type": "integer"
                  },
                  "to": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sent": {
                      "type": "boolean",
                      "example": true
                    },
                    "to": {
                      "type": "string",
                      "format": "email"
                    },
                    "row_index": {
                      "type": "integer"
                    },
                    "subject": {
                      "type": "string"
                    },
                    "attachment_filename": {
                      "type": "string"
                    },
                    "pages": {
                      "type": "integer"
                    },
                    "message_id": {
                      "type": "string"
                    },
                    "charged": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`MAILMERGE_NOT_DRAFT`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "description": "`TEST_SEND_LIMIT`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`CONNECTION_TEST_FAILED` — the mail server refused the message; `details.kind` says how",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mailmerge/campaigns/{campaign_id}/start": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CampaignIdPath"
        }
      ],
      "post": {
        "operationId": "startMailMergeCampaign",
        "tags": [
          "Mail merge"
        ],
        "summary": "Start sending",
        "description": "Needs a verified sender connection, credits for every valid row, no other campaign\nrunning, and your confirmation that every recipient agreed to receive these emails.\nFollow progress with `GET /jobs/{job_id}`; stop it with `POST /jobs/{job_id}/cancel`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "consent"
                ],
                "properties": {
                  "consent": {
                    "type": "object",
                    "required": [
                      "confirmed"
                    ],
                    "properties": {
                      "confirmed": {
                        "type": "boolean",
                        "enum": [
                          true
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign": {
                      "$ref": "#/components/schemas/MailMergeCampaign"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`MAILMERGE_NOT_DRAFT`, `CONNECTION_NOT_VERIFIED` or `MAILMERGE_CAMPAIGN_ALREADY_ACTIVE`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          }
        }
      }
    },
    "/mailmerge/campaigns/{campaign_id}/recipients": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CampaignIdPath"
        }
      ],
      "get": {
        "operationId": "listMailMergeRecipients",
        "tags": [
          "Mail merge"
        ],
        "summary": "List what happened to each row",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "rendering",
                "sending",
                "sent",
                "failed",
                "rejected",
                "unconfirmed",
                "skipped"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of rows, in row order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign_id": {
                      "type": "string"
                    },
                    "recipients": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MailMergeRecipient"
                      }
                    },
                    "cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/mailmerge/campaigns/{campaign_id}/report": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CampaignIdPath"
        }
      ],
      "get": {
        "operationId": "getMailMergeReport",
        "tags": [
          "Mail merge"
        ],
        "summary": "Get the report CSV",
        "description": "A one-hour link to a CSV with one line per row: row,email,status,error_code,error_message,message_id,pages,sent_at. Kept 30 days.",
        "responses": {
          "200": {
            "description": "A signed link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign_id": {
                      "type": "string"
                    },
                    "download_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "content_type": {
                      "type": "string",
                      "example": "text/csv"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`REPORT_NOT_READY` — the campaign has not finished",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          }
        }
      }
    },
    "/mailmerge/campaigns/{campaign_id}/unsent": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CampaignIdPath"
        }
      ],
      "get": {
        "operationId": "getMailMergeUnsentRows",
        "tags": [
          "Mail merge"
        ],
        "summary": "Get the rows that were not sent",
        "description": "A one-hour link to a CSV of the rows a finished campaign did not send: the list's own columns, then unsent_status, unsent_error_code, unsent_error and original_row. Kept 30 days. Fix the file and use it as a new campaign's input, or create a campaign with `input.retry_of` to send the rows again as they are.",
        "responses": {
          "200": {
            "description": "A signed link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaign_id": {
                      "type": "string"
                    },
                    "unsent_count": {
                      "type": "integer"
                    },
                    "unsent_summary": {
                      "$ref": "#/components/schemas/MailMergeUnsentSummary"
                    },
                    "download_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "content_type": {
                      "type": "string",
                      "example": "text/csv"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`CAMPAIGN_NOT_FOUND`, or `MAILMERGE_UNSENT_NOT_AVAILABLE` — every row was sent, or the rows have expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`REPORT_NOT_READY` — the campaign has not finished",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Create one at https://app.podpdf.com under Settings → API Keys."
      }
    },
    "headers": {
      "XJobId": {
        "description": "The job this PDF came from.",
        "schema": {
          "type": "string"
        }
      },
      "XPdfPages": {
        "description": "How many pages were generated.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "parameters": {
      "CampaignIdPath": {
        "name": "campaign_id",
        "in": "path",
        "required": true,
        "description": "The campaign's `campaign_id` (the same as its `job_id`).",
        "schema": {
          "type": "string"
        }
      },
      "ConnectionIdPath": {
        "name": "connection_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "JobIdPath": {
        "name": "job_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "JobId": {
        "type": "string",
        "example": "9f0a4b78-2c0c-4d14-9b8b-123456789abc"
      },
      "PdfOptions": {
        "type": "object",
        "description": "Rendering options. `width`/`height` need `format: null`, which otherwise wins.",
        "properties": {
          "format": {
            "type": "string",
            "nullable": true,
            "enum": [
              "A3",
              "A4",
              "A5",
              "Legal",
              "Letter",
              "Tabloid",
              null
            ],
            "default": "A4"
          },
          "margin": {
            "type": "object",
            "properties": {
              "top": {
                "type": "string",
                "example": "20mm"
              },
              "right": {
                "type": "string",
                "example": "20mm"
              },
              "bottom": {
                "type": "string",
                "example": "20mm"
              },
              "left": {
                "type": "string",
                "example": "20mm"
              }
            }
          },
          "printBackground": {
            "type": "boolean",
            "default": true
          },
          "scale": {
            "type": "number",
            "default": 1,
            "minimum": 0.1,
            "maximum": 2
          },
          "landscape": {
            "type": "boolean",
            "default": false
          },
          "preferCSSPageSize": {
            "type": "boolean",
            "default": false
          },
          "width": {
            "type": "string",
            "example": "100mm"
          },
          "height": {
            "type": "string",
            "example": "150mm"
          },
          "pageRanges": {
            "type": "string",
            "example": "1-3, 5"
          },
          "displayHeaderFooter": {
            "type": "boolean",
            "default": false
          },
          "headerTemplate": {
            "type": "string"
          },
          "footerTemplate": {
            "type": "string"
          }
        }
      },
      "QuickJobRequest": {
        "type": "object",
        "required": [
          "input_type"
        ],
        "properties": {
          "input_type": {
            "type": "string",
            "enum": [
              "html",
              "markdown",
              "url"
            ]
          },
          "html": {
            "type": "string",
            "description": "Required when `input_type` is `html`."
          },
          "markdown": {
            "type": "string",
            "description": "Required when `input_type` is `markdown`."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Required when `input_type` is `url`. HTTPS only, public hosts only, 10 second\ntimeout, 5 MB limit. Relative assets on the page are not resolved.\n"
          },
          "options": {
            "$ref": "#/components/schemas/PdfOptions"
          },
          "store": {
            "type": "boolean",
            "default": false,
            "description": "Return JSON with a signed link instead of the PDF bytes."
          }
        }
      },
      "StoredPdf": {
        "type": "object",
        "properties": {
          "job_id": {
            "$ref": "#/components/schemas/JobId"
          },
          "pages": {
            "type": "integer",
            "example": 3
          },
          "truncated": {
            "type": "boolean",
            "example": false
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed link, valid one hour. Null when the upload failed."
          },
          "download_url_expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "InlinePdf": {
        "type": "object",
        "description": "A PDF sent with the request instead of uploaded first. Up to 4 MB.",
        "required": [
          "pdf_base64"
        ],
        "properties": {
          "pdf_base64": {
            "type": "string",
            "format": "byte"
          },
          "filename": {
            "type": "string",
            "example": "contract.pdf"
          }
        }
      },
      "ProtectPermissions": {
        "type": "object",
        "description": "What a reader may do with the protected PDF. Everything is allowed unless restricted\nhere; restricting anything needs `owner_password`. Screen readers can always read the\ntext.\n",
        "additionalProperties": false,
        "properties": {
          "print": {
            "type": "string",
            "enum": [
              "full",
              "low",
              "none"
            ],
            "default": "full",
            "description": "`low` allows low-resolution printing only."
          },
          "copy": {
            "type": "boolean",
            "default": true,
            "description": "Copying text and images."
          },
          "modify": {
            "type": "boolean",
            "default": true,
            "description": "Editing the document."
          },
          "annotate": {
            "type": "boolean",
            "default": true,
            "description": "Comments and annotations."
          },
          "fill_forms": {
            "type": "boolean",
            "default": true,
            "description": "Filling in form fields."
          },
          "assemble": {
            "type": "boolean",
            "default": true,
            "description": "Inserting, deleting and rotating pages."
          }
        }
      },
      "ProtectRequest": {
        "type": "object",
        "description": "The PDF — `input` inline (up to 4 MB) or `source_key` from `/pdf/upload-url` (up to\n150 MB) — and at least one of `user_password` and `owner_password`.\n",
        "properties": {
          "input": {
            "$ref": "#/components/schemas/InlinePdf"
          },
          "source_key": {
            "type": "string",
            "description": "Key from `/pdf/upload-url`. Give this or `input`.",
            "example": "pdf-uploads/01HZ…/01J8…pdf"
          },
          "filename": {
            "type": "string",
            "description": "The name of an uploaded PDF, used for the output name.",
            "example": "board-pack.pdf"
          },
          "user_password": {
            "type": "string",
            "format": "password",
            "maxLength": 127,
            "description": "Needed to open the PDF. Up to 127 bytes, no control characters."
          },
          "owner_password": {
            "type": "string",
            "format": "password",
            "maxLength": 127,
            "description": "Needed to change or remove the protection. Required when `permissions` restricts\nanything, and must differ from `user_password`. On its own it needs at least one\nrestriction.\n"
          },
          "permissions": {
            "$ref": "#/components/schemas/ProtectPermissions"
          },
          "options": {
            "type": "object",
            "properties": {
              "output_filename": {
                "type": "string",
                "description": "Without `.pdf`. Defaults to the input name with `-protected`."
              }
            }
          },
          "store": {
            "type": "boolean",
            "default": false,
            "description": "Store the result and return a download link instead of the file."
          }
        }
      },
      "UnlockRequest": {
        "type": "object",
        "description": "The protected PDF — `input` inline or `source_key` — and its password.",
        "required": [
          "password"
        ],
        "properties": {
          "input": {
            "$ref": "#/components/schemas/InlinePdf"
          },
          "source_key": {
            "type": "string",
            "description": "Key from `/pdf/upload-url`. Give this or `input`.",
            "example": "pdf-uploads/01HZ…/01J8…pdf"
          },
          "filename": {
            "type": "string",
            "example": "statement.pdf"
          },
          "password": {
            "type": "string",
            "format": "password",
            "maxLength": 127,
            "description": "The password to open the PDF, or its owner password."
          },
          "options": {
            "type": "object",
            "properties": {
              "output_filename": {
                "type": "string",
                "description": "Without `.pdf`. Defaults to the input name with `-unlocked`."
              }
            }
          },
          "store": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "PdfOutputFile": {
        "type": "object",
        "description": "One file a PDF tool produced.",
        "properties": {
          "index": {
            "type": "integer",
            "example": 0
          },
          "name": {
            "type": "string",
            "example": "scan-001.pdf"
          },
          "pages": {
            "type": "integer",
            "example": 3
          },
          "page_range": {
            "type": "string",
            "description": "The pages of the input this file holds.",
            "example": "1-3"
          },
          "size_bytes": {
            "type": "integer",
            "example": 91822
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "description": "Signed link, valid one hour."
          }
        }
      },
      "PdfOpResult": {
        "type": "object",
        "description": "A stored merge or split result, with a link for each output.",
        "properties": {
          "job_id": {
            "$ref": "#/components/schemas/JobId"
          },
          "operation": {
            "type": "string",
            "enum": [
              "pdf_merge",
              "pdf_split"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "input_pages": {
            "type": "integer",
            "example": 10
          },
          "output_count": {
            "type": "integer",
            "example": 3
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PdfOutputFile"
            }
          },
          "zip_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed link to a ZIP of every output, valid one hour. Null when there is one output."
          },
          "download_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "warnings": {
            "type": "array",
            "description": "`FORM_FIELDS_NOT_PRESERVED`, `SIGNATURE_INVALIDATED`, `BOOKMARKS_NOT_PRESERVED` or `XFA_FORM`.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PdfSecurityResult": {
        "type": "object",
        "description": "A stored protect or unlock result. The one output's link is also given as `download_url`.",
        "properties": {
          "job_id": {
            "$ref": "#/components/schemas/JobId"
          },
          "operation": {
            "type": "string",
            "enum": [
              "pdf_protect",
              "pdf_unlock"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "input_pages": {
            "type": "integer",
            "example": 12
          },
          "output_count": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "description": "Signed link, valid one hour."
          },
          "outputs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PdfOutputFile"
            }
          },
          "zip_url": {
            "type": "string",
            "nullable": true,
            "description": "Always null, as there is one output."
          },
          "download_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "warnings": {
            "type": "array",
            "description": "`SIGNATURE_INVALIDATED`, `SOURCE_REPAIRED` or `NON_ASCII_PASSWORD`.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "EditRequest": {
        "type": "object",
        "description": "The document — `source_key` from `/pdf/upload-url` (up to 150 MB) or `input` inline (up\nto 4 MB) — and the recipe to apply to it.\n",
        "required": [
          "recipe"
        ],
        "properties": {
          "source_key": {
            "type": "string",
            "description": "Key from `/pdf/upload-url`. Give this or `input`.",
            "example": "pdf-uploads/01HZ…/01J8…pdf"
          },
          "filename": {
            "type": "string",
            "description": "Name of the document, with `source_key`."
          },
          "input": {
            "$ref": "#/components/schemas/InlinePdf"
          },
          "recipe": {
            "type": "array",
            "description": "Operations, applied in order. At most 200.",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/EditOperation"
            }
          },
          "commit_token": {
            "type": "string",
            "maxLength": 64,
            "description": "Makes a save safe to retry — the same token returns the first result instead of charging again. Ignored by preflight."
          },
          "options": {
            "type": "object",
            "properties": {
              "output_filename": {
                "type": "string",
                "description": "Name for the saved document, without `.pdf`."
              }
            }
          }
        },
        "example": {
          "source_key": "pdf-uploads/01HZ…/01J8…pdf",
          "recipe": [
            {
              "op": "pages.rotate",
              "pages": "1",
              "degrees": 90
            },
            {
              "op": "bates.stamp",
              "prefix": "ACME-",
              "start": 1,
              "digits": 6
            },
            {
              "op": "metadata.strip"
            }
          ],
          "commit_token": "01JB7QRSTUV",
          "options": {
            "output_filename": "contract-stamped"
          }
        }
      },
      "EditMultipartRequest": {
        "type": "object",
        "description": "The same request as multipart form data, each part its own field.",
        "required": [
          "file",
          "recipe"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "The PDF, up to 4 MB."
          },
          "recipe": {
            "type": "string",
            "description": "The recipe, as a JSON array."
          },
          "options": {
            "type": "string",
            "description": "JSON object, e.g. `{\"output_filename\":\"trimmed\"}`."
          },
          "commit_token": {
            "type": "string",
            "maxLength": 64
          }
        }
      },
      "PageSelector": {
        "description": "Which pages an operation applies to. Either a range string such as `\"2,5-7\"`, read\nagainst the document as it stands at that step of the recipe, or a list of page ids from\n`/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert`\ncreated), which keep pointing at the same page whatever earlier operations did. Leave it\nout to select every page.\n",
        "oneOf": [
          {
            "type": "string",
            "example": "2,5-7"
          },
          {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "pattern": "^[pn]\\d+$"
            },
            "example": [
              "p0",
              "p3"
            ]
          }
        ]
      },
      "HexColor": {
        "type": "string",
        "pattern": "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
        "example": "#1f4e79"
      },
      "MarkPosition": {
        "type": "string",
        "enum": [
          "top-left",
          "top-center",
          "top-right",
          "center-left",
          "center",
          "center-right",
          "bottom-left",
          "bottom-center",
          "bottom-right"
        ]
      },
      "EditFont": {
        "type": "string",
        "description": "The 8 standard PDF fonts, or `unicode` / `unicode-bold` (Noto Sans) for accented, Greek,\nCyrillic and Vietnamese text — swapped in automatically when needed and reported as\n`font_substituted`. CJK and right-to-left scripts are refused.\n",
        "enum": [
          "helvetica",
          "helvetica-bold",
          "helvetica-oblique",
          "times",
          "times-bold",
          "times-italic",
          "courier",
          "courier-bold",
          "unicode",
          "unicode-bold"
        ],
        "default": "helvetica"
      },
      "ImageData": {
        "type": "string",
        "description": "A PNG or JPEG, base64-encoded (a `data:image/...;base64,` prefix is accepted). Up to 4 MB."
      },
      "PdfRect": {
        "type": "object",
        "description": "A rectangle in PDF user space — points, bottom-left origin, page rotation not applied.",
        "required": [
          "x",
          "y",
          "width",
          "height"
        ],
        "properties": {
          "x": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "y": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "width": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 20000
          },
          "height": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 20000
          }
        }
      },
      "EditOperation": {
        "description": "One step of a recipe. `op` says which.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/PagesRotateOp"
          },
          {
            "$ref": "#/components/schemas/PagesDeleteOp"
          },
          {
            "$ref": "#/components/schemas/PagesReorderOp"
          },
          {
            "$ref": "#/components/schemas/PagesExtractOp"
          },
          {
            "$ref": "#/components/schemas/PagesInsertOp"
          },
          {
            "$ref": "#/components/schemas/TextAddOp"
          },
          {
            "$ref": "#/components/schemas/WatermarkTextOp"
          },
          {
            "$ref": "#/components/schemas/WatermarkImageOp"
          },
          {
            "$ref": "#/components/schemas/HeaderFooterOp"
          },
          {
            "$ref": "#/components/schemas/BatesStampOp"
          },
          {
            "$ref": "#/components/schemas/MetadataSetOp"
          },
          {
            "$ref": "#/components/schemas/MetadataStripOp"
          },
          {
            "$ref": "#/components/schemas/SanitizeOp"
          },
          {
            "$ref": "#/components/schemas/FormFillOp"
          },
          {
            "$ref": "#/components/schemas/FormFlattenOp"
          },
          {
            "$ref": "#/components/schemas/AnnotateHighlightOp"
          },
          {
            "$ref": "#/components/schemas/AnnotateNoteOp"
          },
          {
            "$ref": "#/components/schemas/AnnotateStampOp"
          },
          {
            "$ref": "#/components/schemas/AnnotationsFlattenOp"
          }
        ],
        "discriminator": {
          "propertyName": "op",
          "mapping": {
            "pages.rotate": "#/components/schemas/PagesRotateOp",
            "pages.delete": "#/components/schemas/PagesDeleteOp",
            "pages.reorder": "#/components/schemas/PagesReorderOp",
            "pages.extract": "#/components/schemas/PagesExtractOp",
            "pages.insert": "#/components/schemas/PagesInsertOp",
            "text.add": "#/components/schemas/TextAddOp",
            "watermark.text": "#/components/schemas/WatermarkTextOp",
            "watermark.image": "#/components/schemas/WatermarkImageOp",
            "header_footer": "#/components/schemas/HeaderFooterOp",
            "bates.stamp": "#/components/schemas/BatesStampOp",
            "metadata.set": "#/components/schemas/MetadataSetOp",
            "metadata.strip": "#/components/schemas/MetadataStripOp",
            "sanitize": "#/components/schemas/SanitizeOp",
            "form.fill": "#/components/schemas/FormFillOp",
            "form.flatten": "#/components/schemas/FormFlattenOp",
            "annotate.highlight": "#/components/schemas/AnnotateHighlightOp",
            "annotate.note": "#/components/schemas/AnnotateNoteOp",
            "annotate.stamp": "#/components/schemas/AnnotateStampOp",
            "annotations.flatten": "#/components/schemas/AnnotationsFlattenOp"
          }
        }
      },
      "PagesRotateOp": {
        "type": "object",
        "title": "pages.rotate",
        "description": "Rotate pages. Absolute by default; `relative` adds to the current rotation.",
        "required": [
          "op",
          "degrees"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "pages.rotate"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "degrees": {
            "type": "integer",
            "minimum": -270,
            "maximum": 360,
            "multipleOf": 90
          },
          "relative": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "PagesDeleteOp": {
        "type": "object",
        "title": "pages.delete",
        "description": "Delete pages. `pages` is required, and deleting every page is refused. Form fields left with no page are removed too.",
        "required": [
          "op",
          "pages"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "pages.delete"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          }
        }
      },
      "PagesReorderOp": {
        "type": "object",
        "title": "pages.reorder",
        "description": "Put the pages in a new order. `order` must list every page exactly once.",
        "required": [
          "op",
          "order"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "pages.reorder"
            ]
          },
          "order": {
            "$ref": "#/components/schemas/PageSelector"
          }
        }
      },
      "PagesExtractOp": {
        "type": "object",
        "title": "pages.extract",
        "description": "Keep only these pages, in the order given, and drop the rest.",
        "required": [
          "op",
          "pages"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "pages.extract"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          }
        }
      },
      "PagesInsertOp": {
        "type": "object",
        "title": "pages.insert",
        "description": "Insert blank pages. They get ids `n0`, `n1`… that later operations can write on.",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "pages.insert"
            ]
          },
          "at": {
            "description": "A page id such as `p3`, a 1-based page number, or `end` (the default).",
            "oneOf": [
              {
                "type": "string",
                "example": "p3"
              },
              {
                "type": "integer",
                "minimum": 1
              }
            ]
          },
          "position": {
            "type": "string",
            "enum": [
              "before",
              "after"
            ],
            "default": "before"
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200,
            "default": 1
          },
          "size": {
            "description": "A paper size, or `{ width, height }` in points. Defaults to the size of the neighbouring page.",
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "A3",
                  "A4",
                  "A5",
                  "A6",
                  "Letter",
                  "Legal",
                  "Tabloid"
                ]
              },
              {
                "type": "object",
                "required": [
                  "width",
                  "height"
                ],
                "properties": {
                  "width": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 14400
                  },
                  "height": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 14400
                  }
                }
              }
            ]
          }
        }
      },
      "TextAddOp": {
        "type": "object",
        "title": "text.add",
        "description": "Put text on pages — a name, a date, a reference. Multi-line blocks work.",
        "required": [
          "op",
          "text"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "text.add"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "text": {
            "type": "string",
            "maxLength": 2000
          },
          "size": {
            "type": "number",
            "minimum": 4,
            "maximum": 300,
            "default": 12
          },
          "font": {
            "$ref": "#/components/schemas/EditFont"
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "opacity": {
            "type": "number",
            "minimum": 0.05,
            "maximum": 1,
            "default": 1
          },
          "rotation": {
            "type": "number",
            "minimum": -360,
            "maximum": 360,
            "default": 0
          },
          "position": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MarkPosition"
              }
            ],
            "default": "top-left"
          },
          "x": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000,
            "description": "With `y`, places the text at exact coordinates instead of `position`."
          },
          "y": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "margin_pt": {
            "type": "number",
            "minimum": 0,
            "maximum": 300,
            "default": 36
          },
          "line_height": {
            "type": "number",
            "minimum": 1,
            "maximum": 400
          }
        }
      },
      "WatermarkTextOp": {
        "type": "object",
        "title": "watermark.text",
        "description": "A text watermark, centred by default. Same options as `text.add`.",
        "required": [
          "op",
          "text"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "watermark.text"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "text": {
            "type": "string",
            "maxLength": 200
          },
          "size": {
            "type": "number",
            "minimum": 4,
            "maximum": 300,
            "default": 12
          },
          "font": {
            "$ref": "#/components/schemas/EditFont"
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "opacity": {
            "type": "number",
            "minimum": 0.05,
            "maximum": 1,
            "default": 1
          },
          "rotation": {
            "type": "number",
            "minimum": -360,
            "maximum": 360,
            "default": 0
          },
          "position": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MarkPosition"
              }
            ],
            "default": "center"
          },
          "x": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "y": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "margin_pt": {
            "type": "number",
            "minimum": 0,
            "maximum": 300,
            "default": 36
          },
          "line_height": {
            "type": "number",
            "minimum": 1,
            "maximum": 400
          }
        }
      },
      "WatermarkImageOp": {
        "type": "object",
        "title": "watermark.image",
        "description": "Draw an image into the page — a logo, a stamp, a signature image. Permanent; use `annotate.stamp` for one that can be removed. Size it with `width`/`height` or `scale`, not both.",
        "required": [
          "op",
          "data"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "watermark.image"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "data": {
            "$ref": "#/components/schemas/ImageData"
          },
          "width": {
            "type": "number",
            "minimum": 1,
            "maximum": 14400
          },
          "height": {
            "type": "number",
            "minimum": 1,
            "maximum": 14400
          },
          "scale": {
            "type": "number",
            "minimum": 0.01,
            "maximum": 20
          },
          "opacity": {
            "type": "number",
            "minimum": 0.05,
            "maximum": 1,
            "default": 1
          },
          "rotation": {
            "type": "number",
            "minimum": -360,
            "maximum": 360,
            "default": 0
          },
          "position": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MarkPosition"
              }
            ],
            "default": "center"
          },
          "x": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "y": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "margin_pt": {
            "type": "number",
            "minimum": 0,
            "maximum": 300,
            "default": 36
          }
        }
      },
      "HeaderFooterOp": {
        "type": "object",
        "title": "header_footer",
        "description": "A header or footer. `{page}`, `{pages}` and `{date}` in the text are filled in per page, which makes it page numbering too.",
        "required": [
          "op",
          "text"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "header_footer"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "text": {
            "type": "string",
            "maxLength": 200,
            "example": "Page {page} of {pages}"
          },
          "size": {
            "type": "number",
            "minimum": 4,
            "maximum": 300,
            "default": 12
          },
          "font": {
            "$ref": "#/components/schemas/EditFont"
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "opacity": {
            "type": "number",
            "minimum": 0.05,
            "maximum": 1,
            "default": 1
          },
          "position": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MarkPosition"
              }
            ],
            "default": "bottom-center"
          },
          "margin_pt": {
            "type": "number",
            "minimum": 0,
            "maximum": 300,
            "default": 36
          }
        }
      },
      "BatesStampOp": {
        "type": "object",
        "title": "bates.stamp",
        "description": "Sequential Bates numbers, drawn the right way up even on pages scanned sideways. The result reports `next_start` for continuing in another document.",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "bates.stamp"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "prefix": {
            "type": "string",
            "maxLength": 40,
            "example": "ACME-"
          },
          "suffix": {
            "type": "string",
            "maxLength": 40
          },
          "start": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000000000,
            "default": 1
          },
          "digits": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "default": 6
          },
          "size": {
            "type": "number",
            "minimum": 4,
            "maximum": 72,
            "default": 9
          },
          "font": {
            "$ref": "#/components/schemas/EditFont"
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "opacity": {
            "type": "number",
            "minimum": 0.05,
            "maximum": 1,
            "default": 1
          },
          "position": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MarkPosition"
              }
            ],
            "default": "bottom-right"
          },
          "margin_pt": {
            "type": "number",
            "minimum": 0,
            "maximum": 300,
            "default": 36
          }
        }
      },
      "MetadataSetOp": {
        "type": "object",
        "title": "metadata.set",
        "description": "Set document properties. At least one field.",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "metadata.set"
            ]
          },
          "title": {
            "type": "string",
            "maxLength": 500
          },
          "author": {
            "type": "string",
            "maxLength": 500
          },
          "subject": {
            "type": "string",
            "maxLength": 500
          },
          "keywords": {
            "type": "string",
            "maxLength": 500
          },
          "creator": {
            "type": "string",
            "maxLength": 500
          },
          "producer": {
            "type": "string",
            "maxLength": 500
          },
          "creation_date": {
            "type": "string",
            "format": "date-time"
          },
          "modification_date": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MetadataStripOp": {
        "type": "object",
        "title": "metadata.strip",
        "description": "Clear the document properties and the hidden XMP metadata, and remove the objects from the file.",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "metadata.strip"
            ]
          },
          "keep": {
            "type": "array",
            "description": "Properties to leave in place.",
            "items": {
              "type": "string",
              "enum": [
                "title",
                "author",
                "subject",
                "keywords",
                "creator",
                "producer"
              ]
            }
          }
        }
      },
      "SanitizeOp": {
        "type": "object",
        "title": "sanitize",
        "description": "Remove active content, and report what was removed. Everything except external links by default.",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "sanitize"
            ]
          },
          "remove": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "enum": [
                "javascript",
                "embedded_files",
                "open_action",
                "auto_actions",
                "launch_actions",
                "external_links"
              ]
            }
          }
        }
      },
      "FormFillOp": {
        "type": "object",
        "title": "form.fill",
        "description": "Fill form fields by their full name (from `/pdf/inspect` with `for: edit`). Text fields\ntake a string, check boxes `true`/`false`, radio groups and dropdowns one of their\noptions, list boxes one option or a list. XFA forms are refused.\n",
        "required": [
          "op",
          "fields"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "form.fill"
            ]
          },
          "fields": {
            "type": "object",
            "minProperties": 1,
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "boolean"
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              ]
            },
            "example": {
              "applicant.name": "Ada Lovelace",
              "agree": true
            }
          },
          "font": {
            "$ref": "#/components/schemas/EditFont"
          }
        }
      },
      "FormFlattenOp": {
        "type": "object",
        "title": "form.flatten",
        "description": "Make fields part of the page, so they can no longer be edited. Every field that can be, or just those named. Cannot be undone.",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "form.flatten"
            ]
          },
          "fields": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            }
          },
          "font": {
            "$ref": "#/components/schemas/EditFont"
          }
        }
      },
      "AnnotateHighlightOp": {
        "type": "object",
        "title": "annotate.highlight",
        "description": "Highlight either `rects` on one page (up to 200 rectangles, one annotation), or every\nmatch of `text` (one annotation per match, up to 500; all pages unless `pages` narrows\nit). Give one or the other. `TEXT_NOT_FOUND` if the text is not there.\n",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "annotate.highlight"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "rects": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/PdfRect"
            }
          },
          "text": {
            "type": "string",
            "maxLength": 500
          },
          "match_case": {
            "type": "boolean",
            "default": false,
            "description": "Only with `text`."
          },
          "whole_word": {
            "type": "boolean",
            "default": false,
            "description": "Only with `text`."
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "opacity": {
            "type": "number",
            "minimum": 0.05,
            "maximum": 1,
            "default": 1
          },
          "contents": {
            "type": "string",
            "maxLength": 2000,
            "description": "A comment shown with the highlight."
          },
          "author": {
            "type": "string",
            "maxLength": 120
          }
        }
      },
      "AnnotateNoteOp": {
        "type": "object",
        "title": "annotate.note",
        "description": "A sticky note at a point on exactly one page.",
        "required": [
          "op",
          "pages",
          "x",
          "y",
          "contents"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "annotate.note"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "x": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "y": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "contents": {
            "type": "string",
            "maxLength": 2000
          },
          "icon": {
            "type": "string",
            "enum": [
              "Note",
              "Comment",
              "Key",
              "Help",
              "NewParagraph",
              "Paragraph",
              "Insert"
            ],
            "default": "Note"
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "author": {
            "type": "string",
            "maxLength": 120
          },
          "open": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "AnnotateStampOp": {
        "type": "object",
        "title": "annotate.stamp",
        "description": "An image stamp — a signature, an approval mark — as an annotation that can be removed later. On exactly one page. Size it with `width`/`height` or `scale`, not both.",
        "required": [
          "op",
          "pages",
          "data",
          "x",
          "y"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "annotate.stamp"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "data": {
            "$ref": "#/components/schemas/ImageData"
          },
          "x": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "y": {
            "type": "number",
            "minimum": -20000,
            "maximum": 20000
          },
          "width": {
            "type": "number",
            "minimum": 1,
            "maximum": 14400
          },
          "height": {
            "type": "number",
            "minimum": 1,
            "maximum": 14400
          },
          "scale": {
            "type": "number",
            "minimum": 0.01,
            "maximum": 20
          },
          "opacity": {
            "type": "number",
            "minimum": 0.05,
            "maximum": 1,
            "default": 1
          },
          "contents": {
            "type": "string",
            "maxLength": 2000
          },
          "author": {
            "type": "string",
            "maxLength": 120
          }
        }
      },
      "AnnotationsFlattenOp": {
        "type": "object",
        "title": "annotations.flatten",
        "description": "Burn annotations into the page so they can no longer be moved or removed. Form widgets, links and popups are never flattened; sticky notes are skipped and reported. `NO_ANNOTATIONS` if there is nothing to flatten.",
        "required": [
          "op"
        ],
        "additionalProperties": false,
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "annotations.flatten"
            ]
          },
          "pages": {
            "$ref": "#/components/schemas/PageSelector"
          },
          "types": {
            "type": "array",
            "minItems": 1,
            "description": "Annotation subtypes to flatten, e.g. `Highlight`, `Stamp`. All of them if left out.",
            "items": {
              "type": "string",
              "maxLength": 40
            }
          }
        }
      },
      "EditOperationResult": {
        "type": "object",
        "description": "What one operation did (or, in preflight, would do).",
        "properties": {
          "index": {
            "type": "integer",
            "description": "Position in the recipe, from 0."
          },
          "op": {
            "type": "string",
            "example": "bates.stamp"
          },
          "scope": {
            "type": "string",
            "enum": [
              "page",
              "document"
            ],
            "nullable": true
          },
          "resolved_pages": {
            "type": "array",
            "description": "1-based page numbers the operation applied to, at that step.",
            "items": {
              "type": "integer"
            }
          },
          "effects": {
            "type": "object",
            "description": "Operation-specific detail — for example `next_start` for Bates, `placements` for marks, `matches` for a text highlight, `skipped` for a flatten.",
            "additionalProperties": true
          },
          "ok": {
            "type": "boolean",
            "nullable": true,
            "description": "Preflight only. `null` when the operation was not evaluated."
          },
          "blocking": {
            "type": "boolean",
            "description": "Preflight only. This operation fails and blocks the save."
          },
          "not_evaluated": {
            "type": "boolean",
            "description": "Preflight only. An earlier operation failed, so this one could not be checked."
          },
          "reasons": {
            "type": "array",
            "description": "Preflight only. Why a blocking operation fails.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "SanitizeReport": {
        "type": "object",
        "description": "What `sanitize` found and removed, by category.",
        "additionalProperties": true
      },
      "EditPreflightResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Whether the recipe would save."
          },
          "operations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EditOperationResult"
            }
          },
          "document": {
            "type": "object",
            "properties": {
              "filename": {
                "type": "string",
                "nullable": true
              },
              "size_bytes": {
                "type": "integer"
              },
              "max_pages": {
                "type": "integer",
                "example": 200
              },
              "page_count_before": {
                "type": "integer"
              },
              "page_count_after": {
                "type": "integer",
                "nullable": true
              },
              "has_form": {
                "type": "boolean"
              },
              "has_xfa": {
                "type": "boolean"
              },
              "signatures_invalidated": {
                "type": "boolean",
                "description": "Saving would break an existing digital signature."
              },
              "bookmarks_lost": {
                "type": "boolean",
                "description": "Bookmarks would not survive a delete or reorder."
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "estimated_output_bytes": {
                "type": "integer",
                "description": "A floor — marks added by the recipe are not counted."
              },
              "output_over_limit": {
                "type": "boolean"
              },
              "sanitize": {
                "$ref": "#/components/schemas/SanitizeReport"
              }
            }
          },
          "cost": {
            "type": "number",
            "description": "What saving would cost, in dollars.",
            "example": 0.0025
          },
          "error": {
            "type": "object",
            "description": "Present when `ok` is false.",
            "properties": {
              "code": {
                "type": "string",
                "example": "PAGE_ID_UNKNOWN"
              },
              "message": {
                "type": "string"
              },
              "index": {
                "type": "integer",
                "nullable": true
              },
              "op": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "EditResult": {
        "type": "object",
        "properties": {
          "job_id": {
            "$ref": "#/components/schemas/JobId"
          },
          "operation": {
            "type": "string",
            "enum": [
              "pdf_edit"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "repeated": {
            "type": "boolean",
            "description": "This `commit_token` was already saved; this is the first result again, not a new charge."
          },
          "input_pages": {
            "type": "integer"
          },
          "output_pages": {
            "type": "integer"
          },
          "output_bytes": {
            "type": "integer"
          },
          "operations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EditOperationResult"
            }
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "description": "Valid for 1 hour. The file is kept for 7 days — fetch a new link with `GET /jobs/{job_id}/download`."
          },
          "download_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sanitize": {
            "$ref": "#/components/schemas/SanitizeReport"
          }
        }
      },
      "InspectResult": {
        "type": "object",
        "properties": {
          "filename": {
            "type": "string",
            "nullable": true
          },
          "size_bytes": {
            "type": "integer"
          },
          "page_count": {
            "type": "integer",
            "nullable": true,
            "description": "`null` for an encrypted file."
          },
          "encrypted": {
            "type": "boolean"
          },
          "has_forms": {
            "type": "boolean",
            "nullable": true
          },
          "has_signatures": {
            "type": "boolean",
            "nullable": true
          },
          "has_xfa": {
            "type": "boolean"
          },
          "first_page": {
            "type": "object",
            "properties": {
              "width": {
                "type": "number"
              },
              "height": {
                "type": "number"
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "max_pages": {
            "type": "integer",
            "description": "The page limit for the path this file would take."
          },
          "pages": {
            "type": "array",
            "description": "With `for: edit`. The ids every recipe refers to pages by.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "example": "p0"
                },
                "index": {
                  "type": "integer"
                },
                "width": {
                  "type": "number"
                },
                "height": {
                  "type": "number"
                },
                "rotation": {
                  "type": "integer",
                  "enum": [
                    0,
                    90,
                    180,
                    270
                  ]
                }
              }
            }
          },
          "form": {
            "type": "object",
            "description": "With `for: edit`. The document's form fields, up to 500.",
            "properties": {
              "has_form": {
                "type": "boolean"
              },
              "has_xfa": {
                "type": "boolean"
              },
              "field_count": {
                "type": "integer"
              },
              "truncated": {
                "type": "boolean"
              },
              "fields": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FormField"
                }
              }
            }
          }
        }
      },
      "FormField": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The full name `form.fill` takes."
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "checkbox",
              "radio",
              "dropdown",
              "optionlist",
              "button",
              "signature",
              "unsupported"
            ]
          },
          "fillable": {
            "type": "boolean"
          },
          "value": {
            "nullable": true,
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "required": {
            "type": "boolean"
          },
          "read_only": {
            "type": "boolean"
          },
          "max_length": {
            "type": "integer",
            "nullable": true
          },
          "multiline": {
            "type": "boolean"
          },
          "multiselect": {
            "type": "boolean"
          },
          "page_id": {
            "type": "string",
            "nullable": true
          },
          "page_index": {
            "type": "integer",
            "nullable": true
          },
          "rect": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PdfRect"
              }
            ],
            "nullable": true
          },
          "widgets": {
            "type": "integer"
          },
          "flattenable": {
            "type": "boolean"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "job_id": {
            "$ref": "#/components/schemas/JobId"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "queued",
              "processing",
              "completed",
              "partial_failed",
              "failed",
              "timeout",
              "cancelled"
            ]
          },
          "job_type": {
            "type": "string",
            "enum": [
              "quick",
              "long",
              "bulk",
              "pdfop",
              "mailmerge"
            ]
          },
          "mode": {
            "type": "string",
            "example": "html"
          },
          "pages": {
            "type": "integer",
            "nullable": true
          },
          "truncated": {
            "type": "boolean"
          },
          "timeout_occurred": {
            "type": "boolean"
          },
          "bundle_type": {
            "type": "string",
            "nullable": true
          },
          "file_count": {
            "type": "integer",
            "nullable": true
          },
          "processed_count": {
            "type": "integer",
            "nullable": true
          },
          "success_count": {
            "type": "integer",
            "nullable": true
          },
          "failure_count": {
            "type": "integer",
            "nullable": true
          },
          "pages_total": {
            "type": "integer",
            "nullable": true
          },
          "cancel_requested": {
            "type": "boolean",
            "description": "Bulk jobs and mail merge campaigns. True once a cancel is asked for; `status` becomes `cancelled` when the job stops."
          },
          "campaign_name": {
            "type": "string",
            "nullable": true,
            "description": "Mail merge campaigns."
          },
          "valid_row_count": {
            "type": "integer",
            "nullable": true,
            "description": "Mail merge campaigns."
          },
          "sent_count": {
            "type": "integer",
            "nullable": true,
            "description": "Mail merge campaigns: emails the mail server accepted."
          },
          "failed_count": {
            "type": "integer",
            "nullable": true,
            "description": "Mail merge campaigns."
          },
          "rejected_count": {
            "type": "integer",
            "nullable": true,
            "description": "Mail merge campaigns."
          },
          "unconfirmed_count": {
            "type": "integer",
            "nullable": true,
            "description": "Mail merge campaigns."
          },
          "skipped_count": {
            "type": "integer",
            "nullable": true,
            "description": "Mail merge campaigns."
          },
          "report_available": {
            "type": "boolean",
            "description": "Mail merge campaigns: the report CSV can be downloaded."
          },
          "unsent_available": {
            "type": "boolean",
            "description": "Mail merge campaigns: rows that were not sent can be downloaded or sent again."
          },
          "unsent_count": {
            "type": "integer",
            "nullable": true,
            "description": "Mail merge campaigns."
          },
          "unsent_summary": {
            "$ref": "#/components/schemas/MailMergeUnsentSummary"
          },
          "outcome_counts": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Mail merge campaigns, once finished: how many rows ended each way, keyed by `sent` or the row's error code."
          },
          "retry_of": {
            "type": "string",
            "nullable": true,
            "description": "Mail merge campaigns: the campaign whose unsent rows this one sends."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "s3_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Present only for jobs with stored output."
          },
          "s3_url_expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "webhook_delivered": {
            "type": "boolean"
          },
          "webhook_delivered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "webhook_retry_count": {
            "type": "integer"
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "error_message": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "MailConnection": {
        "type": "object",
        "properties": {
          "connection_id": {
            "type": "string",
            "example": "01J9A1B2C3D4E5F6G7H8J9K0LM"
          },
          "provider": {
            "type": "string",
            "enum": [
              "smtp"
            ]
          },
          "label": {
            "type": "string",
            "nullable": true,
            "example": "Office 365 billing"
          },
          "from_email": {
            "type": "string",
            "format": "email",
            "example": "billing@acme.com"
          },
          "from_name": {
            "type": "string",
            "nullable": true,
            "example": "Acme Billing"
          },
          "reply_to": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "smtp": {
            "type": "object",
            "properties": {
              "host": {
                "type": "string",
                "example": "smtp.office365.com"
              },
              "port": {
                "type": "integer",
                "enum": [
                  465,
                  587,
                  2525
                ]
              },
              "security": {
                "type": "string",
                "enum": [
                  "tls",
                  "starttls"
                ]
              },
              "username": {
                "type": "string"
              }
            }
          },
          "max_parallel": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "example": 2
          },
          "max_per_minute": {
            "type": "integer",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "unverified",
              "verified",
              "failing"
            ],
            "description": "Campaigns need `verified`, which a successful test email sets."
          },
          "has_secret": {
            "type": "boolean"
          },
          "secret_hint": {
            "type": "string",
            "example": "••••word"
          },
          "verified_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_error": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MailMergeEmail": {
        "type": "object",
        "required": [
          "subject",
          "body_text"
        ],
        "properties": {
          "subject": {
            "type": "string",
            "maxLength": 500,
            "example": "Your statement, {{customer.name}}"
          },
          "body_text": {
            "type": "string",
            "description": "The plain-text part. `{{tokens}}` name template fields or columns (lowercased, other characters become `_`)."
          },
          "body_html": {
            "type": "string",
            "nullable": true,
            "description": "Optional HTML part, sent as written; filled values are HTML-escaped."
          },
          "attachment_filename": {
            "type": "string",
            "nullable": true,
            "description": "Defaults to the template name; `.pdf` is added."
          },
          "list_unsubscribe": {
            "type": "string",
            "nullable": true,
            "description": "A `mailto:` or `https://` target for the List-Unsubscribe header."
          },
          "list_unsubscribe_one_click": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "MailMergeOptions": {
        "type": "object",
        "properties": {
          "dedupe": {
            "type": "boolean",
            "default": true,
            "description": "Send once to an address that appears in several rows."
          }
        }
      },
      "CreateMailMergeCampaign": {
        "type": "object",
        "required": [
          "input"
        ],
        "description": "`template_id`, `connection_id` and `email` are required unless `input.retry_of` is used, in which case they default to the first campaign's.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "template_id": {
            "type": "string"
          },
          "connection_id": {
            "type": "string"
          },
          "input": {
            "type": "object",
            "description": "Exactly one of `rows`, `csv`, `s3_key` or `retry_of`.",
            "properties": {
              "retry_of": {
                "type": "string",
                "description": "A finished campaign of yours: the new campaign sends its unsent rows, with its template, sender, mapping and email unless this request gives others. The mapping is used exactly as saved or given."
              },
              "include": {
                "type": "array",
                "description": "With `retry_of`, which unsent rows to take. Rows with invalid data are never included.",
                "default": [
                  "failed",
                  "not_attempted"
                ],
                "items": {
                  "type": "string",
                  "enum": [
                    "failed",
                    "not_attempted",
                    "rejected",
                    "unconfirmed"
                  ]
                }
              },
              "rows": {
                "type": "array",
                "items": {},
                "description": "Objects keyed by column name, or arrays together with `columns`."
              },
              "columns": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "csv": {
                "type": "string",
                "description": "A CSV string, up to 5 MB."
              },
              "s3_key": {
                "type": "string",
                "description": "From `POST /mailmerge/uploads`."
              }
            }
          },
          "email_column": {
            "type": "string",
            "description": "Detected from the headers when left out."
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Template field key to column name. Unmapped fields are matched by key or label."
          },
          "email": {
            "$ref": "#/components/schemas/MailMergeEmail"
          },
          "options": {
            "$ref": "#/components/schemas/MailMergeOptions"
          },
          "email_notification": {
            "type": "boolean",
            "description": "false skips the \"campaign finished\" email to you."
          },
          "start": {
            "type": "boolean",
            "description": "Queue the campaign now (needs `consent`)."
          },
          "consent": {
            "type": "object",
            "properties": {
              "confirmed": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "MailMergeCampaign": {
        "type": "object",
        "properties": {
          "campaign_id": {
            "type": "string"
          },
          "job_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "queued",
              "processing",
              "completed",
              "partial_failed",
              "failed",
              "cancelled"
            ]
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "template_id": {
            "type": "string"
          },
          "template_name": {
            "type": "string"
          },
          "connection_id": {
            "type": "string"
          },
          "row_count": {
            "type": "integer"
          },
          "valid_row_count": {
            "type": "integer"
          },
          "invalid_row_count": {
            "type": "integer"
          },
          "duplicate_count": {
            "type": "integer"
          },
          "processed_count": {
            "type": "integer"
          },
          "sent_count": {
            "type": "integer"
          },
          "failed_count": {
            "type": "integer"
          },
          "rejected_count": {
            "type": "integer"
          },
          "unconfirmed_count": {
            "type": "integer"
          },
          "skipped_count": {
            "type": "integer"
          },
          "pages_total": {
            "type": "integer"
          },
          "report_available": {
            "type": "boolean"
          },
          "unsent_available": {
            "type": "boolean",
            "description": "Rows that were not sent can be downloaded or sent again."
          },
          "unsent_count": {
            "type": "integer"
          },
          "unsent_summary": {
            "$ref": "#/components/schemas/MailMergeUnsentSummary"
          },
          "outcome_counts": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Once finished: how many rows ended each way, keyed by `sent` or the row's error code."
          },
          "retry_of": {
            "type": "string",
            "nullable": true,
            "description": "The campaign whose unsent rows this one sends."
          },
          "cancel_requested": {
            "type": "boolean"
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "error_message": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "email_column": {
            "type": "string"
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "email": {
            "$ref": "#/components/schemas/MailMergeEmail"
          },
          "options": {
            "$ref": "#/components/schemas/MailMergeOptions"
          }
        }
      },
      "MailMergeUnsentSummary": {
        "type": "object",
        "nullable": true,
        "description": "Rows a finished campaign did not send, by reason. Null when every row was sent.",
        "properties": {
          "failed": {
            "type": "integer",
            "description": "The email could not be sent, or its PDF could not be made."
          },
          "not_attempted": {
            "type": "integer",
            "description": "The campaign stopped or was cancelled first."
          },
          "rejected": {
            "type": "integer",
            "description": "The mail server refused the address."
          },
          "unconfirmed": {
            "type": "integer",
            "description": "The connection dropped after sending; the email may have arrived."
          },
          "invalid": {
            "type": "integer",
            "description": "Invalid address or missing data. In the CSV only."
          }
        }
      },
      "MailMergeRowIssue": {
        "type": "object",
        "properties": {
          "row_index": {
            "type": "integer",
            "description": "0 is the first data row"
          },
          "row": {
            "type": "integer",
            "description": "Spreadsheet row number (the header is row 1)"
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "column": {
                  "type": "string"
                },
                "error": {
                  "type": "string"
                }
              }
            }
          },
          "duplicate_of": {
            "type": "integer"
          }
        }
      },
      "MailMergeValidation": {
        "type": "object",
        "properties": {
          "row_count": {
            "type": "integer"
          },
          "valid_row_count": {
            "type": "integer"
          },
          "invalid_row_count": {
            "type": "integer"
          },
          "duplicate_count": {
            "type": "integer"
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "column_keys": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Column name to the token key email text can use."
          },
          "email_column": {
            "type": "string"
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "suggested_mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "unmapped_required_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "unknown_tokens": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "invalid_rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MailMergeRowIssue"
            }
          },
          "duplicates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MailMergeRowIssue"
            }
          }
        }
      },
      "MailMergePreview": {
        "type": "object",
        "properties": {
          "row_index": {
            "type": "integer"
          },
          "row": {
            "type": "integer"
          },
          "to": {
            "type": "string"
          },
          "valid": {
            "type": "boolean"
          },
          "row_count": {
            "type": "integer"
          },
          "subject": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "attachment_filename": {
            "type": "string"
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "template_html": {
            "type": "string"
          },
          "page": {
            "type": "object",
            "additionalProperties": true
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "MailMergeRecipient": {
        "type": "object",
        "properties": {
          "row_index": {
            "type": "integer"
          },
          "row": {
            "type": "integer"
          },
          "email": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "rendering",
              "sending",
              "sent",
              "failed",
              "rejected",
              "unconfirmed",
              "skipped"
            ],
            "description": "`unconfirmed`: the connection dropped after the message went out; it may have been delivered and is not sent again."
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "error_message": {
            "type": "string",
            "nullable": true
          },
          "message_id": {
            "type": "string",
            "nullable": true
          },
          "pages": {
            "type": "integer",
            "nullable": true
          },
          "attempts": {
            "type": "integer"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "CancelJobResult": {
        "type": "object",
        "properties": {
          "job_id": {
            "$ref": "#/components/schemas/JobId"
          },
          "status": {
            "type": "string",
            "enum": [
              "cancelled",
              "processing"
            ]
          },
          "cancel_requested": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string",
            "example": "The job stops before its next file. PDFs already made are kept in the output and charged."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "example": "INSUFFICIENT_CREDITS"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      }
    },
    "requestBodies": {
      "EditRequest": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/EditRequest"
            }
          },
          "multipart/form-data": {
            "schema": {
              "$ref": "#/components/schemas/EditMultipartRequest"
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request 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"
            }
          }
        }
      },
      "UpgradeRequired": {
        "description": "`UPGRADE_REQUIRED` — the account has no plan yet",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "`INSUFFICIENT_CREDITS`, `CONVERSION_TYPE_NOT_ENABLED`, `PDF_TOOLS_NOT_ENABLED`,\n`RATE_LIMIT_EXCEEDED` or `ACCOUNT_NOT_FOUND`. Note that a plan's rate limit is a 403,\nnot a 429.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource for this account",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Gone": {
        "description": "`OUTPUT_EXPIRED` — the file has been deleted",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "The request body is over the limit",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The values did not validate; `error.details` says which",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PdfOpTimeout": {
        "description": "`PDFOP_TIMEOUT` — the request took longer than 25 seconds. Nothing is charged.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PdfUnprocessable": {
        "description": "The request is well formed but this PDF or password cannot satisfy it: `INVALID_PDF`,\n`PDF_NO_PAGES`, `PDF_ENCRYPTED` (already protected; unlock it first),\n`PDF_PASSWORD_INCORRECT` (also when the owner password is needed:\n`details.needs`), `PDF_NOT_ENCRYPTED`, `PDF_ENCRYPTION_UNSUPPORTED` or\n`PDF_SECURITY_FAILED`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Platform throttling, shared across accounts. Back off and retry with jitter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "Rendering or the service failed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
