---
title: "See what a recipe would do"
method: POST
path: "/pdf/edit/preflight"
tags: ["PDF editor"]
---

# See what a recipe would do

`POST /pdf/edit/preflight`

## Request

- Base URL: `https://api.podpdf.com`
- URL: `https://api.podpdf.com/pdf/edit/preflight`
- Auth: API key in header `X-API-Key`

Runs the recipe against the document without writing anything and reports, for each
operation, the pages it resolves to and what it would do — or why it cannot. Free, and
never recorded as a job. A bad recipe still returns `200` with `ok: false`; the failing
operation is marked `blocking` and everything after it `not_evaluated`.

## Request body

- EditRequest: The document — `source_key` from `/pdf/upload-url` (up to 150 MB) or `input` inline (up to 4 MB) — and the recipe to apply to it.
  - `source_key` string: Key from `/pdf/upload-url`. Give this or `input`.
  - `filename` string: Name of the document, with `source_key`.
  - `input` InlinePdf: A PDF sent with the request instead of uploaded first. Up to 4 MB.
    - `pdf_base64` string, byte, required
    - `filename` string
  - `recipe` EditOperation[], required: Operations, applied in order. At most 200.
    - union: One step of a recipe. `op` says which.
      - PagesRotateOp: Rotate pages. Absolute by default; `relative` adds to the current rotation.
        - `op` 'pages.rotate', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `degrees` integer, required
        - `relative` boolean
      - PagesDeleteOp: Delete pages. `pages` is required, and deleting every page is refused. Form fields left with no page are removed too.
        - `op` 'pages.delete', required
        - `pages` union, required: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
      - PagesReorderOp: Put the pages in a new order. `order` must list every page exactly once.
        - `op` 'pages.reorder', required
        - `order` union, required: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
      - PagesExtractOp: Keep only these pages, in the order given, and drop the rest.
        - `op` 'pages.extract', required
        - `pages` union, required: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
      - PagesInsertOp: Insert blank pages. They get ids `n0`, `n1`… that later operations can write on.
        - `op` 'pages.insert', required
        - `at` union: A page id such as `p3`, a 1-based page number, or `end` (the default).
          - string
          - integer
        - `position` 'before' | 'after'
        - `count` integer
        - `size` union: A paper size, or `{ width, height }` in points. Defaults to the size of the neighbouring page.
          - 'A3' | 'A4' | 'A5' | 'A6' | 'Letter' | 'Legal' | 'Tabloid'
          - object
            - `width` number, required
            - `height` number, required
      - TextAddOp: Put text on pages — a name, a date, a reference. Multi-line blocks work.
        - `op` 'text.add', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `text` string, required
        - `size` number
        - `font` 'helvetica' | 'helvetica-bold' | 'helvetica-oblique' | 'times' | 'times-bold' | 'times-italic' | 'courier' | 'courier-bold' | 'unicode' | 'unicode-bold': The 8 standard PDF fonts, or `unicode` / `unicode-bold` (Noto Sans) for accented, Greek, Cyrillic and Vietnamese text — swapped in automatically when needed and reported as `font_substituted`. CJK and right-to-left scripts are refused.
        - `color` string
        - `opacity` number
        - `rotation` number
        - `position` 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
        - `x` number: With `y`, places the text at exact coordinates instead of `position`.
        - `y` number
        - `margin_pt` number
        - `line_height` number
      - WatermarkTextOp: A text watermark, centred by default. Same options as `text.add`.
        - `op` 'watermark.text', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `text` string, required
        - `size` number
        - `font` 'helvetica' | 'helvetica-bold' | 'helvetica-oblique' | 'times' | 'times-bold' | 'times-italic' | 'courier' | 'courier-bold' | 'unicode' | 'unicode-bold': The 8 standard PDF fonts, or `unicode` / `unicode-bold` (Noto Sans) for accented, Greek, Cyrillic and Vietnamese text — swapped in automatically when needed and reported as `font_substituted`. CJK and right-to-left scripts are refused.
        - `color` string
        - `opacity` number
        - `rotation` number
        - `position` 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
        - `x` number
        - `y` number
        - `margin_pt` number
        - `line_height` number
      - WatermarkImageOp: 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.
        - `op` 'watermark.image', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `data` string, required: A PNG or JPEG, base64-encoded (a `data:image/...;base64,` prefix is accepted). Up to 4 MB.
        - `width` number
        - `height` number
        - `scale` number
        - `opacity` number
        - `rotation` number
        - `position` 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
        - `x` number
        - `y` number
        - `margin_pt` number
      - HeaderFooterOp: A header or footer. `{page}`, `{pages}` and `{date}` in the text are filled in per page, which makes it page numbering too.
        - `op` 'header_footer', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `text` string, required
        - `size` number
        - `font` 'helvetica' | 'helvetica-bold' | 'helvetica-oblique' | 'times' | 'times-bold' | 'times-italic' | 'courier' | 'courier-bold' | 'unicode' | 'unicode-bold': The 8 standard PDF fonts, or `unicode` / `unicode-bold` (Noto Sans) for accented, Greek, Cyrillic and Vietnamese text — swapped in automatically when needed and reported as `font_substituted`. CJK and right-to-left scripts are refused.
        - `color` string
        - `opacity` number
        - `position` 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
        - `margin_pt` number
      - BatesStampOp: Sequential Bates numbers, drawn the right way up even on pages scanned sideways. The result reports `next_start` for continuing in another document.
        - `op` 'bates.stamp', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `prefix` string
        - `suffix` string
        - `start` integer
        - `digits` integer
        - `size` number
        - `font` 'helvetica' | 'helvetica-bold' | 'helvetica-oblique' | 'times' | 'times-bold' | 'times-italic' | 'courier' | 'courier-bold' | 'unicode' | 'unicode-bold': The 8 standard PDF fonts, or `unicode` / `unicode-bold` (Noto Sans) for accented, Greek, Cyrillic and Vietnamese text — swapped in automatically when needed and reported as `font_substituted`. CJK and right-to-left scripts are refused.
        - `color` string
        - `opacity` number
        - `position` 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
        - `margin_pt` number
      - MetadataSetOp: Set document properties. At least one field.
        - `op` 'metadata.set', required
        - `title` string
        - `author` string
        - `subject` string
        - `keywords` string
        - `creator` string
        - `producer` string
        - `creation_date` string, date-time
        - `modification_date` string, date-time
      - MetadataStripOp: Clear the document properties and the hidden XMP metadata, and remove the objects from the file.
        - `op` 'metadata.strip', required
        - `keep` string[]: Properties to leave in place.
      - SanitizeOp: Remove active content, and report what was removed. Everything except external links by default.
        - `op` 'sanitize', required
        - `remove` string[]
      - FormFillOp: Fill form fields by their full name (from `/pdf/inspect` with `for: edit`). Text fields take a string, check boxes `true`/`false`, radio groups and dropdowns one of their options, list boxes one option or a list. XFA forms are refused.
        - `op` 'form.fill', required
        - `fields` object, required
        - `font` 'helvetica' | 'helvetica-bold' | 'helvetica-oblique' | 'times' | 'times-bold' | 'times-italic' | 'courier' | 'courier-bold' | 'unicode' | 'unicode-bold': The 8 standard PDF fonts, or `unicode` / `unicode-bold` (Noto Sans) for accented, Greek, Cyrillic and Vietnamese text — swapped in automatically when needed and reported as `font_substituted`. CJK and right-to-left scripts are refused.
      - FormFlattenOp: 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.
        - `op` 'form.flatten', required
        - `fields` string[]
        - `font` 'helvetica' | 'helvetica-bold' | 'helvetica-oblique' | 'times' | 'times-bold' | 'times-italic' | 'courier' | 'courier-bold' | 'unicode' | 'unicode-bold': The 8 standard PDF fonts, or `unicode` / `unicode-bold` (Noto Sans) for accented, Greek, Cyrillic and Vietnamese text — swapped in automatically when needed and reported as `font_substituted`. CJK and right-to-left scripts are refused.
      - AnnotateHighlightOp: Highlight either `rects` on one page (up to 200 rectangles, one annotation), or every match of `text` (one annotation per match, up to 500; all pages unless `pages` narrows it). Give one or the other. `TEXT_NOT_FOUND` if the text is not there.
        - `op` 'annotate.highlight', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `rects` PdfRect[]
          - `x` number, required
          - `y` number, required
          - `width` number, required
          - `height` number, required
        - `text` string
        - `match_case` boolean: Only with `text`.
        - `whole_word` boolean: Only with `text`.
        - `color` string
        - `opacity` number
        - `contents` string: A comment shown with the highlight.
        - `author` string
      - AnnotateNoteOp: A sticky note at a point on exactly one page.
        - `op` 'annotate.note', required
        - `pages` union, required: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `x` number, required
        - `y` number, required
        - `contents` string, required
        - `icon` 'Note' | 'Comment' | 'Key' | 'Help' | 'NewParagraph' | 'Paragraph' | 'Insert'
        - `color` string
        - `author` string
        - `open` boolean
      - AnnotateStampOp: 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.
        - `op` 'annotate.stamp', required
        - `pages` union, required: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `data` string, required: A PNG or JPEG, base64-encoded (a `data:image/...;base64,` prefix is accepted). Up to 4 MB.
        - `x` number, required
        - `y` number, required
        - `width` number
        - `height` number
        - `scale` number
        - `opacity` number
        - `contents` string
        - `author` string
      - AnnotationsFlattenOp: 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.
        - `op` 'annotations.flatten', required
        - `pages` union: Which pages an operation applies to. Either a range string such as `"2,5-7"`, read against the document as it stands at that step of the recipe, or a list of page ids from `/pdf/inspect` (`p0` is the first original page; `n0`, `n1`… are pages a `pages.insert` created), which keep pointing at the same page whatever earlier operations did. Leave it out to select every page.
          - string
          - string[]
        - `types` string[]: Annotation subtypes to flatten, e.g. `Highlight`, `Stamp`. All of them if left out.
  - `commit_token` string: Makes a save safe to retry — the same token returns the first result instead of charging again. Ignored by preflight.
  - `options` object
    - `output_filename` string: Name for the saved document, without `.pdf`.

## Response `200`

What the recipe would do

- EditPreflightResult
  - `ok` boolean: Whether the recipe would save.
  - `operations` EditOperationResult[]
    - `index` integer: Position in the recipe, from 0.
    - `op` string
    - `scope` 'page' | 'document', nullable
    - `resolved_pages` integer[]: 1-based page numbers the operation applied to, at that step.
    - `effects` object: Operation-specific detail — for example `next_start` for Bates, `placements` for marks, `matches` for a text highlight, `skipped` for a flatten.
    - `ok` boolean, nullable: Preflight only. `null` when the operation was not evaluated.
    - `blocking` boolean: Preflight only. This operation fails and blocks the save.
    - `not_evaluated` boolean: Preflight only. An earlier operation failed, so this one could not be checked.
    - `reasons` object[]: Preflight only. Why a blocking operation fails.
      - `code` string
      - `message` string
  - `document` object
    - `filename` string, nullable
    - `size_bytes` integer
    - `max_pages` integer
    - `page_count_before` integer
    - `page_count_after` integer, nullable
    - `has_form` boolean
    - `has_xfa` boolean
    - `signatures_invalidated` boolean: Saving would break an existing digital signature.
    - `bookmarks_lost` boolean: Bookmarks would not survive a delete or reorder.
    - `warnings` string[]
    - `estimated_output_bytes` integer: A floor — marks added by the recipe are not counted.
    - `output_over_limit` boolean
    - `sanitize` SanitizeReport: What `sanitize` found and removed, by category.
  - `cost` number: What saving would cost, in dollars.
  - `error` object: Present when `ok` is false.
    - `code` string
    - `message` string
    - `index` integer, nullable
    - `op` string, nullable

## Other responses

- `400`: The request was invalid
- `401`: Missing or invalid API key
- `403`: `INSUFFICIENT_CREDITS`, `CONVERSION_TYPE_NOT_ENABLED`, `PDF_TOOLS_NOT_ENABLED`, `RATE_LIMIT_EXCEEDED` or `ACCOUNT_NOT_FOUND`. Note that a plan's rate limit is a 403, not a 429.
- `404`: No such resource for this account
- `422`: The values did not validate; `error.details` says which

---

[API](https://skmtc.dev/podpdf/apis/podpdf-api.md) · [All operations](https://skmtc.dev/podpdf/apis/podpdf-api/llms.txt) · [OpenAPI document](https://skmtc.dev/podpdf/apis/podpdf-api/revisions/84eb21f3ee27?raw)
