REST API  ·  v1

PQ PDF Developer API

All 83 PDF tools — merge, split, OCR, scan, eSign, convert, redact, edit — available programmatically over HTTPS with API-key authentication, IP whitelisting, and per-key rate limits.

83 Operations API-Key Auth IP Whitelisting Rate Limiting Stateful Sessions multipart/form-data TLS 1.3

Overview

The PQ PDF API is a REST gateway that proxies authenticated requests to the same processing engine that powers pqpdf.com. Every tool available in the web UI is available via the API.

Base URL https://api.pqpdf.com
Client ── POST /v1/{operation} ──▶ api.pqpdf.com (TLS 1.3)① Validate operation name ② Check X-API-Key header ③ Verify client IP against whitelist ④ Check hourly rate limit ⑤ Proxy multipart to api.php ⑥ Log usage async (non-blocking)200 PDF / JSON response ◀────────────┘
Request format: All operations use multipart/form-data POST. JSON responses are returned for metadata/info operations; binary PDF/image data for file-producing operations. The Content-Type and Content-Disposition response headers indicate the output type.
HTTP/3 required. api.pqpdf.com is HTTP/3-only. HTTP/2 and HTTP/1.1 requests receive 426 Upgrade Required. Use --http3-only with curl, curl_cffi with http_version=3 in Python, or CURL_HTTP_VERSION_3 in PHP. The proxy strips h2 from its TCP ALPN advertisement for this host — clients that negotiate h2 are downgraded to h1.1 and immediately rejected.

Authentication

Every request (except GET /v1/health and GET /v1/operations) requires an API key passed in the X-API-Key request header. Keys are shown only once at creation time — store them securely.

HeaderValueRequired
X-API-Key Your API key — format pqpdf_<48 hex chars> Yes
X-Session-Id Any opaque string (UUID recommended) — required for stateful operations to bind PHP session cookies Stateful ops only
IP whitelisting: Keys can have zero or more IP/CIDR ranges attached. When a key has any IP entries, requests from non-whitelisted IPs are rejected with 403. Keys with no IP entries accept requests from any IP.
Keep keys secret. Keys are stored as SHA-256 hashes — if lost, revoke and generate a new one. Never include keys in client-side code or public repositories.

Rate Limits

Limits are tracked per API key, per calendar hour (UTC). Polling and pagination operations are exempt to avoid counter inflation.

LimitDefaultOverride
Requests / hour 100 Set per key at creation time via rate_limit_per_hour
Requests / day 500 Set per key at creation time via rate_limit_per_day

Exempt from rate counting: edit-ping, edit-page, pdf-scan-poll, esign-status, esign-preview

When a rate limit is exceeded the API returns 429 Too Many Requests with a JSON error body.
Processing at scale? On-premise deployment removes all per-key rate limits and lifts the 50 MB file size cap — built for high-volume document pipelines, CI/CD workflows, and regulated environments where you own the infrastructure. Explore Enterprise →

Error Codes

All error responses include a JSON body: {"success": false, "error": "message"}

200
Success — body is the operation result (PDF binary or JSON)
400
Bad Request — unknown operation name, or invalid parameter value
401
Unauthorized — missing or invalid X-API-Key, or key is disabled
403
Forbidden — client IP not in the key's whitelist
404
Not Found — key ID or IP entry not found (key management routes)
422
Unprocessable — operation-level error from api.php (wrong file type, corrupted PDF, etc.)
426
Upgrade Required — request used HTTP/1.1 or HTTP/2; retry with HTTP/3 (Upgrade: h3 header indicates the required protocol)
429
Too Many Requests — hourly rate limit exceeded for this key
500
Internal Server Error — proxy or processing failure

Request Format

All operation requests are POST to /v1/{operation} using multipart/form-data. Upload the PDF as the file field and any operation parameters as additional text fields.

curl --http3-only -X POST https://api.pqpdf.com/v1/compress \
  -H "X-API-Key: pqpdf_your_key_here" \
  -F "file=@document.pdf" \
  -F "quality=ebook" \
  -o compressed.pdf
from curl_cffi.requests import Session

with Session(http_version=3) as client:
    resp = client.post(
        "https://api.pqpdf.com/v1/compress",
        headers={"X-API-Key": "pqpdf_your_key_here"},
        files={"file": open("document.pdf", "rb")},
        data={"quality": "ebook"},
    )
with open("compressed.pdf", "wb") as f:
    f.write(resp.content)
// Node 18+ — undici fetch (HTTP/3 via QUIC, Alt-Svc negotiation)
import { fetch, Agent } from "undici";
import fs from "fs";

const dispatcher = new Agent({ allowH2: true });

const form = new FormData();
form.append("file", new Blob([fs.readFileSync("document.pdf")]));
form.append("quality", "ebook");

const resp = await fetch("https://api.pqpdf.com/v1/compress", {
    method: "POST",
    headers: { "X-API-Key": "pqpdf_your_key_here" },
    body: form,
    dispatcher,
});
fs.writeFileSync("compressed.pdf", Buffer.from(await resp.arrayBuffer()));
$ch = curl_init("https://api.pqpdf.com/v1/compress");
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_3,
    CURLOPT_HTTPHEADER     => ["X-API-Key: pqpdf_your_key_here"],
    CURLOPT_POSTFIELDS     => [
        "file"    => new CURLFile("document.pdf"),
        "quality" => "ebook",
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
file_put_contents("compressed.pdf", curl_exec($ch));
curl_close($ch);

Key Management

Create and manage API keys and their IP whitelists. All routes require a valid X-API-Key and operate on that key's owner account.

GET /v1/keys List all keys for your account

Returns all API keys associated with the authenticated user's account.

Response
{
  "success": true,
  "keys": [
    {
      "id":                  "uuid",
      "label":               "primary",
      "key_prefix":          "pqpdf_02ef5e22",
      "is_active":           true,
      "created_at":          "2026-04-09T17:31:45Z",
      "last_used_at":        "2026-04-09T19:00:00Z",
      "rate_limit_per_hour": 100,
      "rate_limit_per_day":  500,
      "ip_count":            2
    }
  ]
}

Generates a new API key. The full key is returned only once — store it securely.

Body: JSON (Content-Type: application/json)
FieldTypeRequiredDescription
labelstringNoHuman-readable label. Defaults to "default"
rate_limit_per_hourintegerNoOverride default hourly limit for this key
rate_limit_per_dayintegerNoOverride default daily limit for this key
Response
{
  "success": true,
  "message": "Save this key — it will not be shown again.",
  "key": {
    "id":                  "uuid",
    "label":               "production",
    "key_prefix":          "pqpdf_a1b2c3d4",
    "api_key":             "pqpdf_a1b2c3d4...",
    "rate_limit_per_hour": 200,
    "rate_limit_per_day":  1000
  }
}

Permanently disables a key. You cannot revoke the key that is making the request.

Response
{ "success": true, "revoked": "uuid" }

IP Whitelist

Attach IP addresses or CIDR ranges to a key. When any entries exist, requests from IPs not matching any entry are rejected with 403.

{
  "success": true,
  "ips": [
    { "id": "uuid", "cidr": "203.0.113.0/24", "label": "office", "created_at": "..." }
  ]
}
FieldTypeRequiredDescription
cidrstringYesIPv4/IPv6 address or CIDR range, e.g. 203.0.113.5 or 10.0.0.0/8
labelstringNoHuman-readable label for this entry
{ "success": true, "ip": { "id": "uuid", "cidr": "203.0.113.5", "label": "my-server" } }
{ "success": true, "removed": "uuid" }

Usage Stats

GET /v1/usage Last 30 days, grouped by operation
{
  "success": true,
  "current_key_id":       "uuid",
  "usage_this_hour":      12,
  "rate_limit_per_hour":  100,
  "last_30_days": [
    { "operation": "compress", "total": 48, "avg_ms": 210.5, "total_bytes": 9437184 },
    { "operation": "get-info", "total": 24, "avg_ms":  85.2, "total_bytes":   12288 }
  ]
}

List Operations

GET /v1/operations All supported operations — no auth required

Returns the full list of allowed operation names and which require a session ID.

{
  "success": true,
  "operations": ["merge", "split", "compress", ...],
  "stateful_operations": ["edit-init", "edit-page", "esign-create", ...],
  "note": "For stateful operations, include X-Session-Id header with a UUID."
}

Core PDF Tools

All operations: POST /v1/{operation} with X-API-Key header and multipart/form-data body. Fields marked file are binary uploads; all others are text fields.

Merge & Split
merge
Combine multiple PDFs into one
→ PDF
split
Split PDF by page ranges into a ZIP
→ ZIP
split_chunk
Split into fixed-size chunks (ZIP)
→ ZIP
extract-pages
Extract specific pages to a new PDF
→ PDF
delete-pages
Remove pages from a PDF
→ PDF
reorder
Reorder pages by index array
→ PDF
Optimise & Transform
compress
Reduce file size with Ghostscript
→ PDF
rotate
Rotate all or specified pages
→ PDF
flatten
Flatten form fields / annotations
→ PDF
grayscale
Convert to grayscale
→ PDF
repair
Attempt to repair a corrupted PDF
→ PDF
pdfa
Convert to PDF/A archival format
→ PDF
Watermark, Security & Info
watermark
Add text or image watermark
→ PDF
sign
Apply a visual signature stamp
→ PDF
protect
Password-protect a PDF
→ PDF
unlock
Remove password (requires password)
→ PDF
get-info
Metadata, page count, encryption info
→ JSON
extract-text
Extract all text content
→ TXT
to-images
Render pages to images (ZIP)
→ ZIP
workflow
Chain multiple operations in one call
→ PDF
compress — Parameters
FieldTypeRequiredValues / DefaultDescription
filefileYesPDF to compress
qualitystringNoscreen | ebook | printer | prepress | default — default ebookGhostscript PDFSETTINGS level (screen=72dpi, ebook=150dpi, printer=300dpi, prepress=300dpi+colour preservation)
custom_dpiintegerNo50–600Override image DPI instead of using a quality preset
strip_metadatastringNo1Remove all metadata from the output PDF
linearizestringNo1Linearize (fast web view) via qpdf — enables progressive browser rendering
merge — Parameters
FieldTypeRequiredDescription
file[]file[]YesTwo or more PDF files (send as repeated file[] fields)
split — Parameters
FieldTypeRequiredDescription
filefileYesPDF to split
modestringNoall (one PDF per page) | interval (every N pages) | custom (split after specified pages) — default all
intervalintegerNoPages per chunk when mode=interval
rangesstringNoComma-separated page ranges for mode=custom, e.g. 1-3,4,5-7
rotate — Parameters
FieldTypeRequiredDescription
filefileYesPDF to rotate
anglenumberYesRotation angle. Multiples of 90° are lossless; other values trigger rasterization.
scopestringNoall | odd | even | custom — default all
pagesstringNoPage range when scope=custom, e.g. 1,3-5
grayscale — Parameters
FieldTypeRequiredDescription
filefileYesPDF to convert
modestringNograyscale (8-bit gray) | bw (1-bit binary) — default grayscale
repair — Parameters
FieldTypeRequiredDescription
filefileYesPDF to repair
linearizestringNo1 to linearize for fast web view after repair
remove_invalidstringNo1 to strip invalid objects
pdfa — Parameters
FieldTypeRequiredDescription
filefileYesPDF to convert
levelstringNo1b | 2b | 3b — default 2b
to-images — Parameters
FieldTypeRequiredDescription
filefileYesPDF to render
formatstringNopng | jpeg — default png
dpiintegerNo72 | 96 | 150 | 300 — default 150
pagesstringNoPage range, e.g. 1-3,5. Omit for all pages.
extract-text — Parameters
FieldTypeRequiredDescription
filefileYesPDF to extract text from
layoutstringNo1 to preserve spatial layout in the output (uses pdftotext -layout flag)
protect — Parameters
FieldTypeRequiredDescription
filefileYesPDF to protect
user_passwordstringNo*Password required to open the PDF. At least one password required.
owner_passwordstringNo*Owner/permissions password. Auto-generated if only user_password is given.
no_printstringNo1 to disable printing
no_copystringNo1 to disable text copying
no_editstringNo1 to disable editing
unlock — Parameters
FieldTypeRequiredDescription
filefileYesEncrypted PDF
passwordstringYesOwner or user password (RC4-40/128, AES-128/256 all supported)
workflow — Parameters
FieldTypeRequiredDescription
filefileYesInput PDF
stepsstring (JSON)YesJSON array of step objects: [{"op":"compress","quality":"ebook"},{"op":"watermark","text":"DRAFT"}]

Available workflow operations: rotate, compress, watermark, protect, unlock, grayscale, flatten, repair, extract-pages, delete-pages, reorder, pdfa, sign, redact, split-every. Each step object uses the same parameter names as the standalone operation.

flatten — Parameters
FieldTypeRequiredDescription
filefileYesPDF to flatten. Burns all form fields and annotations permanently into page content.
get-info — Parameters
FieldTypeRequiredDescription
filefileYesPDF to inspect. Returns version, page count, dimensions, encryption status, metadata (title, author, subject, keywords, creator, producer), file size, linearization flag.
extract-pages — Parameters
FieldTypeRequiredDescription
filefileYesSource PDF
pagesstringYesPages to keep, e.g. 1,3-5,8
delete-pages — Parameters
FieldTypeRequiredDescription
filefileYesSource PDF. At least one page must remain after deletion.
pagesstringYesPages to remove, e.g. 2,4-6
reorder — Parameters
FieldTypeRequiredDescription
filefileYesSource PDF
orderstringYesNew page order as comma-separated 1-based indices, e.g. 3,1,2,5,4
split_chunk — Parameters
FieldTypeRequiredDescription
filefileYesPDF to chunk
chunkintegerYesPages per output chunk
sign — Parameters
FieldTypeRequiredDescription
filefileYesPDF to sign
sig_typestringYesdraw | type | image — visual signature mode
sig_imagefileNo*PNG/JPEG signature image. Required when sig_type=image.
sig_textstringNo*Typed name. Required when sig_type=type.
sig_datastringNo*Base64-encoded PNG of drawn signature. Required when sig_type=draw.
h_positionstringNoleft | center | right — default center
v_positionstringNotop | middle | bottom — default bottom
sizeintegerNoSignature size in pt, 40–300 — default 120
pageintegerNoPage to sign (1-based). Default: last page.
cryptostringNo1 to embed an RSA-2048/SHA-256 digital certificate alongside the visual signature
certfileNoPKCS#12 (.p12/.pfx) certificate bundle. If omitted with crypto=1, a self-signed cert is auto-generated.
cert_passstringNoPassphrase for the uploaded certificate bundle
signer_namestringNoSigner name embedded in the certificate fields
reasonstringNoSignature reason field
locationstringNoSignature location field
watermark — Parameters
FieldTypeRequiredDescription
filefileYesPDF to watermark
textstringNo*Watermark text. Required unless wm_file provided.
wm_filefileNo*Image watermark (PNG/JPEG/SVG). Required unless text provided.
opacityfloatNo0.01.0, default 0.3
positionstringNocenter | diagonal | top-left | top-right | bottom-left | bottom-right
colorstringNoHex color e.g. #ff0000 (text watermarks)
font_sizeintegerNoFont size in pt
pagesstringNoPage range. Omit for all pages.

Conversion

Convert between PDF and Office formats, images, HTML, and Markdown.

word-to-pdf
DOCX / DOC / ODT / RTF → PDF
→ PDF
excel-to-pdf
XLSX / XLS / ODS → PDF
→ PDF
ppt-to-pdf
PPTX / PPT / ODP → PDF
→ PDF
image-to-pdf
PNG / JPG / WEBP / TIFF → PDF
→ PDF
html-to-pdf
HTML file or URL → PDF
→ PDF
pdf-to-excel
Extract tables to XLSX
→ XLSX
pdf-to-ppt
Pages → PPTX slides
→ PPTX
pdf-to-html
PDF → HTML with layout
→ ZIP
pdf-to-md
PDF → Markdown text
→ MD
convert
Generic format conversion
→ varies
redact
Permanently redact text/regions
→ PDF
compare
Visual diff of two PDFs
→ JSON/PDF
excel-get-sheets
List sheet names in XLSX
→ JSON
ppt-get-slides
List slide titles in PPTX
→ JSON
word-to-pdf — Parameters
FieldTypeRequiredDescription
filefileYesDOCX, DOC, ODT, or RTF file to convert to PDF
ppt-to-pdf — Parameters
FieldTypeRequiredDescription
filefileYesPPTX, PPT, or ODP file to convert to PDF
pdf-to-excel — Parameters
FieldTypeRequiredDescription
filefileYesPDF to extract tables from. Output is an XLSX file.
pdf-to-ppt — Parameters
FieldTypeRequiredDescription
filefileYesPDF to convert — each page becomes a PPTX slide.
pdf-to-html — Parameters
FieldTypeRequiredDescription
filefileYesPDF to convert to HTML. Output is a ZIP archive containing HTML + assets.
pdf-to-md — Parameters
FieldTypeRequiredDescription
filefileYesPDF to convert to Markdown text.
convert — Parameters
FieldTypeRequiredDescription
filefileYesSource file in any supported input format
output_formatstringYesTarget format: pdf | docx | xlsx | pptx | html | md | txt | png | jpg
excel-get-sheets — Parameters
FieldTypeRequiredDescription
filefileYesXLSX or XLS file. Returns a JSON array of sheet names.
ppt-get-slides — Parameters
FieldTypeRequiredDescription
filefileYesPPTX or PPT file. Returns a JSON array of slide titles.
html-to-pdf — Parameters
FieldTypeRequiredDescription
filefileNo*HTML file to convert. Required unless url provided.
urlstringNo*URL to fetch and convert to PDF. Required unless file provided.
page_sizestringNoA4 | Letter | Legal etc.
orientationstringNoportrait | landscape
redact — Parameters
FieldTypeRequiredDescription
filefileYesPDF to redact
termsstringNo*Comma-separated text patterns to find and redact. Required unless regex or regions provided.
regexstringNo*Regex pattern to redact. At least one of terms, regex, or regions required.
regionsstring (JSON)No*JSON array of bounding-box regions: [{"page":1,"x":50,"y":100,"w":200,"h":30}]
case_sensitivestringNo1 for case-sensitive text matching
whole_wordstringNo1 to match whole words only
pagesstringNoPage range to search, e.g. 1-3,5. Omit for all pages.
colorstringNoRedaction fill colour: black | white — default black
compare — Parameters
FieldTypeRequiredDescription
filefileYesOriginal PDF
file2fileYesRevised PDF to compare against
dpiintegerNoRender resolution for pixel comparison: 72300 — default 150
sensitivityintegerNoPixel-difference threshold 1–50 — default 10. Lower = stricter.

Response header X-Pdf-Compare-Stats contains compared, different, and identical page counts. Output is a PDF with page-by-page diff images (red = removed, green = added, grey = unchanged).

image-to-pdf — Parameters
FieldTypeRequiredDescription
file[]file[]Yes1–50 images (JPG, PNG, TIFF, WebP, BMP, GIF)
page_sizestringNoA4 | Letter | original — default A4
orientationstringNoportrait | landscape | auto — default auto
excel-to-pdf — Parameters
FieldTypeRequiredDescription
filefileYesXLSX, XLS, ODS, or CSV file
page_sizestringNoA4 | Letter | content (fit-to-data) — default A4
orientationstringNoportrait | landscape | auto — default auto
skip_empty_sheetsstringNo1 to omit empty worksheets from the output

OCR

POST /v1/ocr OCR a scanned PDF or image with Tesseract
FieldTypeRequiredDescription
filefileYesPDF or image (PNG/JPEG/TIFF)
langstringNoTesseract language code(s), e.g. eng, eng+fra. Default eng
outputstringNopdf (searchable PDF) | txt | hocr. Default pdf
dpiintegerNoRender DPI before OCR, default 300
pagesstringNoPage range to OCR. Omit for all.

PDF Editor Stateful

The editor operates on a live in-memory PDF session. Start with edit-init, make changes with subsequent calls sharing the same X-Session-Id, then call edit-apply to render and download the final PDF.

Session binding: Include X-Session-Id: <your-uuid> on every editor call. The session is kept alive for 30 minutes after the last request. Poll with edit-ping to prevent expiry.
edit-init
Upload PDF, get page thumbnails + token
→ JSON
edit-page
Get rendered page image
→ PNG
edit-apply
Apply queued edits, download PDF
→ PDF
edit-search
Search text in open document
→ JSON
edit-doc-op
Queue a document operation (add text, image, shape…)
→ JSON
edit-qr-generate
Insert a QR code onto a page
→ JSON
edit-new-pdf
Start a new blank document in session
→ JSON
edit-ping
Heartbeat — keeps session alive
→ JSON
edit-get-vectors
Extract vector paths from a page
→ JSON
edit-active-content-inspect
Detect active content (JS, forms)
→ JSON
edit-active-content-remove
Strip all active content
→ JSON
edit-attachment-list
List embedded file attachments
→ JSON
edit-attachment-add
Embed a file attachment
→ JSON
edit-attachment-get
Download an embedded attachment
→ file
edit-attachment-del
Remove an embedded attachment
→ JSON
edit-layer-list
List optional content layers
→ JSON
edit-layer-set
Toggle layer visibility
→ JSON
edit-layer-del
Delete an optional content layer
→ JSON
edit-init — Parameters
FieldTypeRequiredDescription
filefileYesPDF to open in the editor session
passwordstringNoUser password if the PDF is encrypted
edit-doc-op — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
op_typestringYesadd_text | add_image | add_shape | delete_element | move_element | resize_element | add_link
pageintegerYes1-based page number
datastring (JSON)YesOperation-specific payload (coordinates, text content, styles, etc.) as JSON string
edit-page — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
pageintegerYes1-based page number to render
dpiintegerNoRender resolution — default 150
edit-search — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
querystringYesText string to search for in the open document
edit-qr-generate — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
datastringYesText or URL to encode in the QR code
pageintegerYes1-based page number to place the QR code on
xnumberYesHorizontal position in points from left edge
ynumberYesVertical position in points from top edge
sizeintegerNoQR code size in points — default 100
edit-new-pdf — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token — resets the session to a new blank document
page_sizestringNoA4 | Letter | Legal etc. — default A4
edit-get-vectors — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
pageintegerYes1-based page number to extract vector paths from
edit-active-content-inspect — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
edit-active-content-remove — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
edit-attachment-add — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
filefileYesFile to embed as an attachment inside the PDF
namestringNoDisplay name for the attachment. Defaults to the uploaded filename.
edit-attachment-get — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
namestringYesName of the embedded attachment to download
edit-attachment-del — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
namestringYesName of the embedded attachment to remove
edit-layer-set — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
layerstringYesLayer name as returned by edit-layer-list
visiblestringYes1 to show the layer, 0 to hide it
edit-layer-del — Parameters
FieldTypeRequiredDescription
tokenstringYesSession token from edit-init
layerstringYesLayer name to permanently delete

PDF Forensics Scanner

Deep forensic analysis — 44 detection engines, YARA rules, sandbox emulation, ML scoring, and AI-generated forensic summary. Large files are scanned asynchronously.

pdf-scan
Synchronous scan (small files)
→ JSON
pdf-scan-start
Start async scan, get token
stateful
pdf-scan-poll
Poll async scan result by token
stateful
pdf-sanitize
Strip all active content (JS, macros…)
→ PDF
pdf-scan-feedback
Submit label feedback for ML training
→ JSON
pdf-scan — Parameters
FieldTypeRequiredDescription
filefileYesPDF to scan
passwordstringNoPassword for encrypted PDFs
pdf-scan-start — Parameters
FieldTypeRequiredDescription
filefileYesPDF to scan asynchronously
passwordstringNoPassword for encrypted PDFs
pdf-scan-start — Response
{ "started": true, "token": "pdftool_abc123..." }
pdf-scan-poll — Parameters
FieldTypeRequiredDescription
tokenstringYesToken from pdf-scan-start
pdf-scan-poll — Response (pending)
{ "ready": false, "progress": "Extracting embedded objects..." }
pdf-scan-poll — Response (complete)
{
  "ready": true,
  "risk_score": 82,
  "risk_level": "high-risk",
  "ai_forensic_summary": {
    "threat_verdict": "SUSPICIOUS",
    "confidence": "MEDIUM",
    "executive_summary": "PDF contains obfuscated JavaScript with network callbacks.",
    "key_findings": [
      { "signal": "JavaScript eval() with base64 payload", "severity": "HIGH", "mitre_id": "T1059.007" }
    ],
    "recommended_actions": ["Do not open this file", "Submit to sandbox for dynamic analysis"]
  }
}
pdf-sanitize — Parameters
FieldTypeRequiredDescription
filefileYesPDF to sanitize. All JavaScript, macros, embedded executables, and active content are stripped from the output.
pdf-scan-feedback — Parameters
FieldTypeRequiredDescription
tokenstringYesScan token from pdf-scan-start / pdf-scan-poll
labelstringYesbenign | malicious — correct classification for ML training

The synchronous pdf-scan operation accepts files up to 10 MB; larger files must use the async pdf-scan-start / pdf-scan-poll flow (up to 50 MB). On-premise removes the cap entirely →


Office Forensics

Deep forensic scanning and surgical sanitization for Office documents (.docx, .xlsx, .pptx, .doc, .xls, .ppt, .xlsm, .docm, .pptm, .rtf, .msg, .eml and more). Eight dedicated engines cover macros, XLM, OLE objects, metadata, IOCs, and container structure — with an AI-generated forensic summary. Sanitize endpoints return a cleaned file; the scanner uses an async job queue.

Different URL scheme. Office endpoints use dedicated routes (/v1/office-scan, /v1/office-sanitize/*) rather than the generic /v1/{operation} pattern used by PDF tools. Authentication, IP whitelisting, and rate limits are identical.
POST /v1/office-scan
Submit document — returns job_id instantly
→ JSON
GET /v1/office-scan/:job_id
Poll scan status / fetch full report
→ JSON
POST /v1/office-sanitize/pdf
Convert to static PDF — destroys all active content
→ PDF
POST /v1/office-sanitize/macro
Strip VBA & XLM macros, preserve content
→ File
POST /v1/office-sanitize/meta
Remove all document metadata
→ File
POST /v1/office-sanitize/ooxml
Convert legacy OLE2 (.doc/.xls/.ppt) to OOXML
→ File
POST /v1/office-scan — Submit

Submit an Office document for forensic analysis. Returns immediately; poll for results.

FieldTypeRequiredDescription
filefileYesOffice document to scan (.docx, .xlsx, .pptx, .doc, .xls, .ppt, .xlsm, .docm, .pptm, .rtf, .msg, .eml, .one, .vsdx…)
enginesstringNoComma-separated engine list (default: all 20). Values: container, crypto, metadata, relationships, embedded, macros, xlm, ole, ioc, clamav, yara, threat_intel, libreoffice, sandbox, entropy, opc, schema, fonts, signatures, nlp, correlation, ai
Response
{ "job_id": "b42f57f8-449e-4a3f-97dc-baec65ac03a2", "status": "queued" }
GET /v1/office-scan/:job_id — Poll

Poll scan status. Rate-exempt — polling does not consume your hourly quota. Returns scanning while in progress; full JSON report on completion.

Response — in progress
{ "status": "scanning" }
Response — complete
{
  "status": "complete",
  "result": {
    "file_info": { "filename": "report.docx", "size_bytes": 38000, "format": "DOCX" },
    "risk_assessment": {
      "level": "CRITICAL",
      "total_score": 95,
      "findings_count": 8
    },
    "all_findings": [
      { "engine": "macros", "severity": "critical", "finding": "VBA AutoOpen macro detected", "mitre": "T1137" },
      { "engine": "ioc",    "severity": "high",     "finding": "Suspicious URL: http://evil.example/payload" }
    ],
    "ai_forensic_summary": {
      "threat_verdict": "MALICIOUS",
      "confidence": "HIGH",
      "executive_summary": "Document contains an AutoOpen macro that downloads a remote payload.",
      "recommended_actions": ["Do not open", "Submit to sandbox"]
    }
  }
}
Risk level thresholds (score cap 999) — the risk_assessment.level field is determined as follows:
  • CRITICAL — any critical-severity finding, or total score ≥ 150
  • HIGH — 2+ high-severity findings, or total score ≥ 75
  • MEDIUM — total score ≥ 35
  • LOW — total score ≥ 8
  • CLEAN — total score < 8
The Correlation engine runs last and operates bidirectionally: it adds score when multiple engines confirm an attack pattern (dropper chain, C2 beacon, template injection, etc.), and subtracts score when all primary detection engines (ClamAV, YARA, threat intel, sandbox, macros, embedded, NLP) are clean and the only non-zero score originates from weak cosmetic signals — preventing isolated metadata quirks from inflating the verdict.
POST /v1/office-sanitize/pdf — Convert to static PDF

Render via LibreOffice to a flat PDF. Destroys all macros, VBA, XLM, OLE objects, and active content with certainty. The safest sanitization option. Returns application/pdf.

FieldTypeRequiredDescription
filefileYesOffice document to convert
POST /v1/office-sanitize/macro — Strip macros

Surgically remove all VBA and XLM macros while preserving document content, formatting, and structure. Returns the sanitized document in the same format as the input.

FieldTypeRequiredDescription
filefileYesOffice document to sanitize
POST /v1/office-sanitize/meta — Strip metadata

Remove all document metadata: author, revision history, last-saved-by, company name, and custom properties. Returns the sanitized document in the same format as the input.

FieldTypeRequiredDescription
filefileYesOffice document to sanitize
POST /v1/office-sanitize/ooxml — Convert OLE2 → OOXML

Upgrade legacy .doc / .xls / .ppt (OLE2 binary format) to modern .docx / .xlsx / .pptx (OOXML). Eliminates the OLE2 exploit surface while preserving document content.

FieldTypeRequiredDescription
filefileYesLegacy OLE2 Office document (.doc, .xls, .ppt)
All sanitize endpoints return the cleaned file as a binary download with a Content-Disposition: attachment; filename=…_sanitized.* header. The content-type header reflects the output format (application/pdf for the pdf mode; application/octet-stream otherwise).
curl Example — scan & poll
# Submit
curl --http3-only https://api.pqpdf.com/v1/office-scan \
  -H "X-API-Key: YOUR_KEY" \
  -F "file=@report.docx"
# → {"job_id":"b42f57f8...","status":"queued"}

# Poll (no rate-limit cost)
curl --http3-only https://api.pqpdf.com/v1/office-scan/b42f57f8-449e-4a3f-97dc-baec65ac03a2 \
  -H "X-API-Key: YOUR_KEY"
curl Example — sanitize (PDF conversion)
curl --http3-only https://api.pqpdf.com/v1/office-sanitize/pdf \
  -H "X-API-Key: YOUR_KEY" \
  -F "file=@report.docx" \
  -o report_safe.pdf

🔬 File Fingerprint Comparator

Submit two PDF or Office documents for independent scanning, then diff their structural fingerprints across 25+ security features — VBA macros, IOC extraction, OLE structure, YARA matches, ClamAV verdict, encryption, JavaScript, XFA, URL/IP counts, threat intelligence, risk scores, and more. Returns a similarity percentage, variant verdict (IDENTICAL / NEAR_IDENTICAL / SIMILAR / PARTIALLY_SIMILAR / DIFFERENT), and a differences-first field-level comparison table. Use it for malware variant detection, suspicious attachment triage, and document integrity verification.

Three-step workflow. Submit each file independently with POST /v1/file-compare/scan (one call per file), poll both GET /v1/file-compare/scan/{job_id} until status = complete, then call GET /v1/file-compare/diff with the two job IDs. Both scan polls are rate-exempt.
POST /v1/file-compare/scan
Submit a file — returns job_id instantly
→ JSON
GET /v1/file-compare/scan/:job_id
Poll scan status — rate-exempt
→ JSON
GET /v1/file-compare/diff
Diff two completed scans — rate-exempt
→ JSON
POST /v1/file-compare/scan — Submit a file

Submit one PDF or Office document for forensic fingerprint scanning. Call this twice — once per file — to get two job IDs for the diff step.

FieldTypeRequiredDescription
filefileYesPDF or Office document (.pdf, .docx, .xlsx, .pptx, .doc, .xls, .ppt, .xlsm, .docm, .pptm, .xlsb, .rtf, .one, .vsdx, .vsdm, .msg, .eml, .ics, .mdb, .accdb). Max 10 MB.
Response
{ "job_id": "b42f57f8-449e-4a3f-97dc-baec65ac03a2", "status": "queued" }
PDF files return a job_id prefixed pdf__ (e.g. pdf__pdftool_a1b2c3...). Office files return a plain UUID. Both formats are accepted by the poll and diff endpoints.
GET /v1/file-compare/scan/:job_id — Poll status

Poll the scan status of a submitted file. Rate-exempt — polling does not consume your hourly quota. Call until status = complete before requesting the diff.

Response — in progress
{ "status": "scanning" }
Response — complete
{ "status": "complete" }
Response — error
{ "status": "error", "error": "Scan failed: unsupported file type" }
GET /v1/file-compare/diff — Compute fingerprint diff

Compute the structural fingerprint diff between two completed scans. Both job IDs must be in complete status. Rate-exempt.

Query paramTypeRequiredDescription
job_astringYesjob_id returned by the scan for File A
job_bstringYesjob_id returned by the scan for File B
Response
{
  "similarity_pct": 87.4,
  "verdict": "SIMILAR",
  "file_a": { "filename": "invoice_v1.docx", "format": "DOCX", "risk_score": 12 },
  "file_b": { "filename": "invoice_v2.docx", "format": "DOCX", "risk_score": 45 },
  "diff": [
    {
      "group": "Macro",
      "feature": "has_macros",
      "file_a": false,
      "file_b": true,
      "match": false
    },
    {
      "group": "IOC",
      "feature": "url_count",
      "file_a": 0,
      "file_b": 3,
      "match": false
    }
  ],
  "matching": [
    { "group": "Container", "feature": "container_type", "value": "OOXML", "match": true }
  ]
}
curl Example — full workflow
# Step 1 — submit File A
curl --http3-only -X POST https://api.pqpdf.com/v1/file-compare/scan \
  -H "X-API-Key: YOUR_KEY" \
  -F "file=@invoice_v1.docx"
# → {"job_id":"b42f57f8-449e-4a3f-97dc-baec65ac03a2","status":"queued"}

# Step 2 — submit File B
curl --http3-only -X POST https://api.pqpdf.com/v1/file-compare/scan \
  -H "X-API-Key: YOUR_KEY" \
  -F "file=@invoice_v2.docx"
# → {"job_id":"c91a3d12-8f4e-4b5a-ae01-d7bc22fe1098","status":"queued"}

# Step 3 — poll until both are complete
curl --http3-only "https://api.pqpdf.com/v1/file-compare/scan/b42f57f8-449e-4a3f-97dc-baec65ac03a2" \
  -H "X-API-Key: YOUR_KEY"
# → {"status":"complete"}

# Step 4 — compute diff
curl --http3-only "https://api.pqpdf.com/v1/file-compare/diff?job_a=b42f57f8-449e-4a3f-97dc-baec65ac03a2&job_b=c91a3d12-8f4e-4b5a-ae01-d7bc22fe1098" \
  -H "X-API-Key: YOUR_KEY"

Form Fill Stateful

Fill PDF form fields programmatically. Initialize the session, then apply field values.

fill-init
Load PDF, get form field list
→ JSON
fill-apply
Apply field values, download filled PDF
→ PDF
fill-init — Parameters
FieldTypeRequiredDescription
filefileYesPDF with fillable form fields
fill-init — Response
{
  "success": true,
  "token": "pdftool_abc123",
  "fields": [
    { "name": "first_name", "type": "text", "value": "" },
    { "name": "agree",      "type": "checkbox", "value": "Off" }
  ]
}
fill-apply — Parameters
FieldTypeRequiredDescription
tokenstringYesToken from fill-init
fieldsstring (JSON)YesJSON object mapping field names to values, e.g. {"first_name":"Alice","agree":"On"}
flattenstringNo1 to flatten fields after filling (makes them non-editable)

Outline / Bookmarks Stateful

outline-init
Load PDF, return bookmark tree
→ JSON
outline-apply
Write modified bookmark tree, download PDF
→ PDF
outline-init — Parameters
FieldTypeRequiredDescription
filefileYesPDF to load. Returns the current bookmark tree as a nested JSON array.
passwordstringNoPassword for encrypted PDFs
outline-apply — Parameters
FieldTypeRequiredDescription
tokenstringYesToken from outline-init
outlinestring (JSON)YesModified bookmark tree (same structure as returned by outline-init)

eSign Workflow Stateful

Create multi-party signature workflows. Up to 10 signers (sequential or parallel). The requester can lock signing field controls, assign multiple Sign Here / Initial Here boxes per signer per page, and enforce PAdES-B cryptographic signatures. Each signer gets a unique secure link — no account needed. Track progress with esign-status; download the completed PDF with esign-download.

esign-create
Upload document, create envelope
→ JSON
esign-add-signer
Add a signer to the envelope
→ JSON
esign-remove-signer
Remove a signer from the envelope
→ JSON
esign-sign
Apply a signer's signature
→ JSON
esign-status
Get envelope and signer status
→ JSON
esign-preview
Render preview page
→ PNG
esign-download
Download completed signed PDF
→ PDF
esign-resume
Resume a partially signed envelope
→ JSON
esign-cancel
Cancel and void an envelope
→ JSON
esign-create — Parameters
FieldTypeRequiredDescription
filefileYesPDF document to be signed (max 50 MB)
signersJSON stringYesArray of signer objects, max 10: [{"name":"Alice","email":"alice@example.com"},{"name":"Bob","email":""}]
orderstringNosequential (default) or parallel
require_crypto1NoRequire a PAdES-B cryptographic signature from every signer
signer_placementsJSON stringNoLock Sign Here / Initial Here boxes per signer per page. Object keyed by signer index (0-based): {"0":[{"page":1,"pos_x_pct":0.6,"pos_y_pct":0.8,"type":"sign"},{"page":2,"pos_x_pct":0.1,"pos_y_pct":0.9,"type":"initial"}]}. type: "sign" (120 pt) or "initial" (70 pt). Signer's page shows clickable Sign Here / Initial Here boxes — signer clicks each to confirm; counter tracks progress; Submit unlocks when all boxes confirmed. Position params in esign-sign are ignored.
field_rulesJSON stringNoLock or pre-select any signer control. Each key: {"mode":"free"|"preselect"|"required","value":"..."}. Keys: sig_method (draw/type/upload), date (on/off), time (on/off), page (last/first/all), ink_color (black/navy/blue/dark-gray), stroke_width (thin/medium/thick), crypto (on/off — replaces require_crypto), cert_source (auto/own). Example: {"sig_method":{"mode":"required","value":"draw"},"ink_color":{"mode":"required","value":"black"},"crypto":{"mode":"required","value":"on"}}
esign-add-signer — Parameters
FieldTypeRequiredDescription
tokenstringYesEnvelope token from esign-create
emailstringYesSigner's email address
namestringNoSigner's display name
orderintegerNoSigning order (1 = first). Omit for parallel signing.
esign-remove-signer — Parameters
FieldTypeRequiredDescription
tokenstringYesEnvelope token from esign-create
emailstringYesEmail address of the signer to remove
esign-sign — Parameters
FieldTypeRequiredDescription
workflow_tokenstringYesWorkflow token from esign-create
signer_tokenstringYesThis signer's unique token (from their sign URL)
sig_typestringYesdraw | type | image | none
sig_datastringNo*Data URL of drawn signature (for draw)
sig_imagestringNo*Data URL of uploaded signature image (for image)
sig_textstringNo*Name to render as typed signature (for type)
pos_xstringNoleft | center | right | custom — ignored when workflow has signer_placements for this signer
pos_ystringNotop | middle | bottom | custom — ignored when workflow has signer_placements
pos_x_pctfloatNoHorizontal position 0.0–1.0 (when pos_x=custom)
pos_y_pctfloatNoVertical position 0.0–1.0 (when pos_y=custom)
pagestringNoall | first | last | custom — ignored when workflow has signer_placements
page_numberintNo1-based page number (when page=custom)
sig_sizeintNoSignature width in points 40–300 — ignored when workflow has signer_placements (Sign Here = 120 pt, Initial Here = 70 pt per area)
sig_datestringNoDate string to embed, e.g. "April 11, 2026"
sig_ink_colorstringNoInk colour for text-mode signatures: #1a1a2e (black, default), #1a237e (navy), #1565c0 (blue), #555555 (dark-grey). Ignored when field_rules.ink_color is required.
esign-status — Parameters
FieldTypeRequiredDescription
tokenstringYesEnvelope token from esign-create
esign-preview — Parameters
FieldTypeRequiredDescription
tokenstringYesEnvelope token from esign-create
pageintegerNo1-based page number to render — default 1
esign-download — Parameters
FieldTypeRequiredDescription
tokenstringYesEnvelope token — only succeeds when all signers have signed (status = complete)
esign-resume — Parameters
FieldTypeRequiredDescription
tokenstringYesEnvelope token for the partially signed workflow to resume
esign-cancel — Parameters
FieldTypeRequiredDescription
tokenstringYesEnvelope token to void. Cancellation is permanent and cannot be undone.

Layout

nup
N-up imposition (2, 4, 6, 9 pages per sheet)
→ PDF
deskew
Auto-deskew scanned pages
→ PDF
nup — Parameters
FieldTypeRequiredDescription
filefileYesPDF to impose
layoutstringYes2 | 4 | 6 | 8 | 9 | booklet — pages per output sheet
page_sizestringNoA4 | A3 | Letter — default A4
orientationstringNoportrait | landscape — default portrait
deskew — Parameters
FieldTypeRequiredDescription
filefileYesScanned PDF to correct
modestringNoboth | crop | deskew — default both
cornersstring (JSON)NoPer-page corner overrides: [{"page":1,"tl":[x,y],"tr":[x,y],"br":[x,y],"bl":[x,y]}]. Client-detected corners for perspective correction.

Inspection

a11y
Accessibility / PDF/UA compliance check
→ JSON
font-inspect
List embedded fonts, encoding, subsets
→ JSON
color-inspect
Colour space analysis (CMYK, spot colours)
→ JSON
a11y — Parameters
FieldTypeRequiredDescription
filefileYesPDF to check. Returns weighted pass-rate, letter grade (A–F), and per-check WCAG 2.1 criterion with impact level.
font-inspect — Parameters
FieldTypeRequiredDescription
filefileYesPDF to inspect. Returns font name, type (Type1 / TrueType / CIDFont / OpenType), embedded status, and subset flag per font.
color-inspect — Parameters
FieldTypeRequiredDescription
filefileYesPDF to inspect. Returns colour space analysis per page — RGB, CMYK, spot colours, ICC profiles.

Standards

pdfx
Convert to PDF/X (print production)
→ PDF
table-json
Extract tables as structured JSON
→ JSON
pades
Apply PAdES digital signature (PKI)
→ PDF
pdfx — Parameters
FieldTypeRequiredDescription
filefileYesPDF to convert to PDF/X
standardstringNoX-1a | X-3 | X-4 — default X-3
render_intentintegerNo0 (Perceptual) | 1 (RelativeColorimetric) | 2 (Saturation) | 3 (AbsoluteColorimetric) — default 1
table-json — Parameters
FieldTypeRequiredDescription
filefileYesPDF containing tables to extract. Returns a JSON array of tables with row/cell data. Returns an error if no tables are detected.
pades — Parameters
FieldTypeRequiredDescription
filefileYesPDF to sign
certfileYesSigning certificate (PEM or P12/PFX)
keyfileNoPrivate key (PEM) — not required for P12 bundles
passphrasestringNoCertificate passphrase if encrypted
reasonstringNoSignature reason field
locationstringNoSignature location field

AI-Powered Tools

Self-hosted AI analysis — no OpenAI, no external calls. Runs on the local Qwen 2.5 model.

pdf-ai-docinfo
AI-generated document summary and topic extraction
→ JSON
pdf-ai-redact-suggest
AI-suggested redaction targets (PII, sensitive terms)
→ JSON
pdf-ai-compare-explain
AI-narrated diff between two PDF versions
→ JSON
pdf-ai-docinfo — Parameters
FieldTypeRequiredDescription
filefileYesPDF to analyse. Returns AI-generated forensic report: classification, confidence, executive_summary, key_findings, recommended_action, mitre_techniques, false_positive_risk.
pdf-ai-redact-suggest — Parameters
FieldTypeRequiredDescription
filefileYesPDF to analyse. Returns AI-suggested redaction targets — names, SSNs, account numbers, and other PII/sensitive patterns found in the document text.
pdf-ai-compare-explain — Parameters
FieldTypeRequiredDescription
filefileYesOriginal PDF
file2fileYesRevised PDF to compare against

Camera Scan

POST /v1/camera-scan Convert mobile camera photos to a clean PDF
FieldTypeRequiredDescription
file[]file[]Yes1–30 JPEG/PNG photos of physical documents
enhancementstringNoauto | colour | bw | grayscale — default auto. Applied via CLAHE adaptive contrast.
ocrstringNo1 to run Tesseract OCR and embed an invisible text layer in the output PDF
deskewstringNo1 to apply OpenCV perspective correction per page
cornersstring (JSON)NoPer-image corner coordinates for manual perspective correction: [{"tl":[x,y],"tr":[x,y],"br":[x,y],"bl":[x,y]}]

Guide: Stateful Sessions

Editor, form fill, outline, eSign, and async scan operations maintain server-side PHP session state. You must pass a consistent X-Session-Id header across all calls in the same workflow.

Session TTL: 30 minutes of inactivity. Call edit-ping (exempt from rate limits) to keep editor sessions alive during user pauses.
Example: PDF Editor Workflow
SESSION="$(uuidgen)"
KEY="pqpdf_your_key_here"

# 1. Open PDF in editor
curl --http3-only -X POST https://api.pqpdf.com/v1/edit-init \
  -H "X-API-Key: $KEY" -H "X-Session-Id: $SESSION" \
  -F "file=@document.pdf"
# → { "token": "pdftool_abc123", "page_count": 5, ... }

# 2. Add text to page 1
curl --http3-only -X POST https://api.pqpdf.com/v1/edit-doc-op \
  -H "X-API-Key: $KEY" -H "X-Session-Id: $SESSION" \
  -F "token=pdftool_abc123" \
  -F 'op_type=add_text' \
  -F 'page=1' \
  -F 'data={"x":100,"y":200,"text":"APPROVED","font_size":24,"color":"#ff0000"}'

# 3. Download edited PDF
curl --http3-only -X POST https://api.pqpdf.com/v1/edit-apply \
  -H "X-API-Key: $KEY" -H "X-Session-Id: $SESSION" \
  -F "token=pdftool_abc123" \
  -o edited.pdf
import uuid
from curl_cffi.requests import Session

SESSION = str(uuid.uuid4())
KEY     = "pqpdf_your_key_here"
HEADERS = {"X-API-Key": KEY, "X-Session-Id": SESSION}
BASE    = "https://api.pqpdf.com/v1"

with Session(http_version=3) as client:
    # 1. Open PDF
    init  = client.post(f"{BASE}/edit-init", headers=HEADERS,
                        files={"file": open("document.pdf", "rb")}).json()
    token = init["token"]

    # 2. Queue text annotation
    client.post(f"{BASE}/edit-doc-op", headers=HEADERS, data={
        "token":   token,
        "op_type": "add_text",
        "page":    1,
        "data":    '{"x":100,"y":200,"text":"APPROVED","font_size":24,"color":"#ff0000"}',
    })

    # 3. Download result
    resp = client.post(f"{BASE}/edit-apply", headers=HEADERS,
                       data={"token": token})
with open("edited.pdf", "wb") as f:
    f.write(resp.content)

Code Examples

Async PDF Security Scan
KEY="pqpdf_your_key_here"
SESSION="$(uuidgen)"

# Start async scan
TOKEN=$(curl -s --http3-only -X POST https://api.pqpdf.com/v1/pdf-scan-start \
  -H "X-API-Key: $KEY" -H "X-Session-Id: $SESSION" \
  -F "file=@suspect.pdf" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

# Poll until ready
while true; do
  RESULT=$(curl -s --http3-only -X POST https://api.pqpdf.com/v1/pdf-scan-poll \
    -H "X-API-Key: $KEY" -H "X-Session-Id: $SESSION" \
    -F "token=$TOKEN")
  READY=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('ready','false'))")
  [ "$READY" = "True" ] && echo "$RESULT" && break
  sleep 3
done
import uuid, time
from curl_cffi.requests import Session

KEY     = "pqpdf_your_key_here"
SESSION = str(uuid.uuid4())
HEADERS = {"X-API-Key": KEY, "X-Session-Id": SESSION}
BASE    = "https://api.pqpdf.com/v1"

with Session(http_version=3) as client:
    # Start
    resp  = client.post(f"{BASE}/pdf-scan-start", headers=HEADERS,
                        files={"file": open("suspect.pdf", "rb")}).json()
    token = resp["token"]

    # Poll
    while True:
        result = client.post(f"{BASE}/pdf-scan-poll", headers=HEADERS,
                             data={"token": token}).json()
        if result.get("ready"):
            print(result["risk_level"], result["ai_forensic_summary"]["threat_verdict"])
            break
        time.sleep(3)
Key Management
KEY="pqpdf_your_key_here"

# Create a new key
curl -s --http3-only -X POST https://api.pqpdf.com/v1/keys \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"ci-pipeline","rate_limit_per_hour":500}'

# Add an IP to the new key
NEW_KEY_ID="<uuid from above>"
curl -s --http3-only -X POST https://api.pqpdf.com/v1/keys/$NEW_KEY_ID/ips \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"cidr":"10.0.0.0/8","label":"internal-network"}'
from curl_cffi.requests import Session

KEY     = "pqpdf_your_key_here"
HEADERS = {"X-API-Key": KEY}
BASE    = "https://api.pqpdf.com/v1"

with Session(http_version=3) as client:
    # Create key
    new    = client.post(f"{BASE}/keys", headers=HEADERS, json={
        "label": "ci-pipeline", "rate_limit_per_hour": 500
    }).json()
    key_id = new["key"]["id"]
    print("New key:", new["key"]["api_key"])  # save this!

    # Whitelist IP
    client.post(f"{BASE}/keys/{key_id}/ips", headers=HEADERS, json={
        "cidr": "10.0.0.0/8", "label": "internal-network"
    })

On-Premise & Enterprise

Deploy the full API stack inside your own infrastructure. All the same operations, no rate limits, no file size caps, no external calls — your data never leaves your perimeter.

Unlimited requests & file size
Air-gapped / private network deployment
PHI / PII pipeline safe — zero data egress
Custom key quotas & IP policies
One-time setup, annual support
Unlimited users & servers
Common deployment contexts
Get in Touch → No commitment. We will scope your deployment and provide a fixed quote.