Automation port: API reference

Turn the port on in Preferences, Automation. The batch chapter says what it is for.

The automation port is a small HTTP/1.1 API on 127.0.0.1 (port 7431 by default), off until it is turned on in Preferences › Automation.

Rules every request meets

RuleAnswer when broken
Authorization: Bearer <token> with the token from Preferences401
No Origin, Sec-Fetch-* or Access-Control-Request-* header (browsers)403
Host exactly 127.0.0.1:<port> or localhost:<port>400 (missing or repeated), 421
Request line and headers within 16 KB, 64 headers431
Body within 256 KB, with Content-Length (no chunked bodies)413, 411, 501
Headers within 5 s, the body within 15 s of them408
At most 240 requests a minute (a wrong token counts eleven times)429 with Retry-After
At most 8 connections at once503
POST bodies as JSON415
OPTIONS and HEAD405

One request per connection: every answer says Connection: close. No answer carries CORS headers. Every answer is JSON; an error is {"error": "<a sentence>"}.

Routes

GET /v1/health

{"ok": true, "app": "AntiPDF", "version": "1.0.4", "jobs_running": 1, "jobs_waiting": 0}

POST /v1/jobs

Starts a job. The body is a job description:

{
  "recipe": "Invoices in",
  "inputs": ["D:\\Scans\\2026-10", "D:\\Scans\\extra.pdf"],
  "output": {"folder": "D:\\Invoices\\2026-10", "suffix": " (processed)",
             "exists": "number", "keep_folders": true, "log": true},
  "dry_run": false,
  "workers": 2,
  "memory_mb": 2048
}
KeyMeaning
recipeA saved recipe's name. Give this or steps.
stepsThe steps, as in a recipe file (below).
nameWith steps: a name for the log.
inputsFiles and folders (absolute paths), at most 1000. A folder brings the documents in it and its folders; links and junctions in it are not followed.
output.folderWhere the results go; left out or empty: beside each original.
output.suffixAdded to each name; default " (processed)".
output.existsWhen a name is taken: number (default, keep both), skip, replace. An original is never replaced.
output.keep_foldersWith a folder: repeat the folders of the inputs.
output.logA log file beside the results (default true).
dry_runSay what would happen, write nothing.
workersFiles worked on at once, 1 to 16 (default: chosen by the app).
memory_mbThe memory the job may hold at once, 256 to 65536.
certificate_passwordFor a Sign step, when the certificate has a password. Never written anywhere.

Every path must be absolute and inside the folders allowed in Preferences, as written and as it really is (links followed), unless Any folder is on. A job may name at most 10,000 files and 20 GB in all.

Answers: 202 {"id": "1a04c9ebf9b3a442", "state": "running", "href": "/v1/jobs/1a04c9ebf9b3a442"}; 400 a job that cannot run (the sentence says why: an unknown step or option, steps in an order that cannot work); 403 a path outside the allowed folders, or no folder allowed yet; 404 an input that is not there; 503 sixteen jobs waiting already.

GET /v1/jobs

The jobs kept (the last 64 finished, and all running or waiting), newest first: {"jobs": [{"id": "...", "state": "finished"}]}.

GET /v1/jobs/{id}

{
  "id": "1a04c9ebf9b3a442", "state": "finished", "recipe": "Invoices in",
  "dry_run": false, "seconds": 12.4,
  "counts": {"waiting": 0, "working": 0, "done": 2, "skipped": 1, "failed": 0, "stopped": 0},
  "log": "D:\\Invoices\\2026-10\\AntiPDF batch 2026-10-12 09.31.07.log",
  "files": [
    {"input": "D:\\Scans\\2026-10\\a.pdf", "state": "done",
     "message": "3 scanned pages read. 62% smaller (4.1 MB to 1.5 MB).",
     "outputs": ["D:\\Invoices\\2026-10\\a (processed).pdf"]},
    {"input": "D:\\Scans\\2026-10\\b.pdf", "state": "skipped",
     "message": "It is protected with a password. Open it in AntiPDF and save a copy without the password, then add that copy.",
     "outputs": []}
  ]
}

A job's state: waiting (for a place: two run at once), running, stopping, finished, stopped, or refused (with problem: it could not start, for example a Pro step without Pro). A file's state: waiting, working (with step, counted from 1), done, skipped, failed, stopped.

DELETE /v1/jobs/{id}

Stops a job after the steps in progress: 202 {"id": "...", "state": "stopping"}. A job not started yet, or finished, answers 200 with its state.

Steps

Each step is {"do": "<step>", <options>}. Unknown steps and options are refused with their names.

doOptions
convert(only as the first step)
ocr
redact (Pro)find: list of card, bank, id, email, phone, serial, name, address; countries: list of US, GB, DE, FR, ES, IT, NL, SE, CA, AU, JP, BR, IN, MX, PL, AT (left out: as chosen in Security › Redact); lists: names of your redaction lists
compressmax_dpi (0 keeps sizes; 50 to 1200, default 150), quality (10 to 100, default 75)
watermark (Pro)text, place (across, top, bottom), size (small, medium, large), strength (light, medium, strong), colour (grey, red, blue), pages
pdfalevel: "1b", "2b" (the default) or "3b" (not after watermark: its font is not embedded)
extractpages, such as "1-3, 7, 10-"
rotateangle (90, 180, 270), pages (empty: all)
flatten
clean (Pro)remove: list of metadata, scripts, attachments, thumbnails, private, comments, hidden_layers (default: the first three)
sign (Pro)reason, location (only as the last step)
mergename (the joined file's, default "Joined"); once
preflightprofile: anywhere, print, pdfx-1a, pdfx-4, pdfa-1b, pdfa-1a, pdfa-2b, pdfa-2u, pdfa-2a, pdfa-3b, pdfa-3u, pdfa-3a, or own with name and its settings (fonts, min_dpi, max_dpi, colour (any, cmyk), transparency, trim, bleed, structure, anywhere, pdfx (0, 1, 4)); fix (the automatic fixes; not with a PDF/A profile), fail (a file with findings fails)
batesNumber pages: prefix, suffix, digits (1 to 12, default 6), start (0 to 999999999999, default 1), place (top-left, top-centre, top-right, bottom-left, bottom-centre, bottom-right), margin_mm (0 to 100, default 10), font (sans, serif, mono), bold, size (4 to 72 points, default 10), colour (black, red, blue); once, not before redact. The numbers go on from file to file in the order of inputs (files one at a time); each file's result has "bates": [first, last, pages], and a CSV of the ranges is written beside the results
splitevery (pages per part) or ranges ("1-3, 4-10", a part each); once, not before merge
text, markdown(only as the last step)
tablesformat: xlsx (default), csv or markdown; pages (empty: all). Only as the last step; a file with no tables is skipped
find_pageswords (separated by commas), all (every word on the page; default any), match_case, whole_words (default true); or field (a form field's name or part of it) and value (what its value holds; empty: filled in). A file with no page that matches is skipped
assetstake: list of pictures, fonts, attachments (default all). Only as the last step; each file's outputs lists every file written and the report
propertiestitle, author, subject, keywords (an empty one empties it)
bookmarkscontents (a contents page in front, default true), levels (1 to 6, default 2), title (the contents' title). A file with no headings is skipped
repair
accessibilityaction: check (the default), tag or fix; part: 1 (PDF/UA-1, the default) or 2 (PDF/UA-2); fail (check: a file that does not pass fails); lang (tag, fix: the document's language, such as "en-GB"); title (tag, fix); mark (fix: marked PDF/UA when nothing else is left). The log of each file says the verdict and what is left to do by hand

Examples

Make every scan in a folder searchable, as text files next to them:

curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"steps": [{"do": "ocr"}, {"do": "text"}], "inputs": ["D:\\Scans"], "output": {"suffix": ""}}' \
  http://127.0.0.1:7431/v1/jobs

Wait for a job in Python:

import json, time, urllib.request
TOKEN, BASE = "<the token>", "http://127.0.0.1:7431"
def call(method, path, body=None):
    req = urllib.request.Request(BASE + path, method=method,
        data=json.dumps(body).encode() if body is not None else None,
        headers={"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"})
    with urllib.request.urlopen(req) as r:
        return json.load(r)
job = call("POST", "/v1/jobs", {"recipe": "Invoices in", "inputs": [r"D:\Scans\2026-10"],
                                "output": {"folder": r"D:\Invoices\2026-10"}})
while (state := call("GET", "/v1/jobs/" + job["id"]))["state"] in ("waiting", "running", "stopping"):
    time.sleep(2)
for f in state["files"]:
    print(f["state"], f["input"], f.get("message", ""))

Always check redacted copies before you send them.