Inspect
inspect (quality report), info, perceive (perception layer).
Three read endpoints answer three different questions: what is this file
(info), is it any good (inspect), and what does it say, where
(perceive). All take {"pdf": "<base64>"} and return JSON.
PDF=$(base64 -w0 input.pdf) # macOS: base64 -i input.pdfInfo — document properties
POST /v1/pdf/info:
{
"page_count": 3,
"version": "1.7",
"title": "Invoice 2026-001",
"author": "ACME",
"encrypted": false,
"tagged": false,
"form": false,
"page_sizes": [ ... ],
"size_bytes": 182044
}Inspect — quality report
POST /v1/pdf/inspect scores the file's production quality — the checks a
print shop would run:
{
"pages": 3,
"bytes": 182044,
"fonts_embedded": ["Inter-Regular"],
"fonts_not_embedded": [],
"objects": { "text": 214, "image": 2, "vector_path": 31, "other": 4 },
"real_text_chars": 3211,
"min_image_dpi": 220,
"issues": [
{ "severity": "warning", "message": "..." }
],
"score": 92
}score is 0-100; issues carry a severity of critical, warning or
info. Non-embedded fonts and low-DPI images are the classic print killers.
Perceive — the perception layer
POST /v1/pdf/perceive returns a structured, semantically-labelled map of
the document — what an AI agent (or your code) reads before acting with
edit-text, redact or annotate:
curl -X POST https://api.pdf-forge.dev/v1/pdf/perceive \
-H "Authorization: Bearer $PDFFORGE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"pdf\": \"$PDF\", \"detail\": \"full\", \"ocr\": \"auto\"}"const doc = await client.pdf.perceive(pdfBytes, {
detail: "full", // "summary" (default) | "full"
ocr: "auto", // "auto" (default) | "off" | "force"
pages: [1], // optional 1-based filter
});The result: summary, page_count, coords (the coordinate contract) and
per page —
elements— every text span with a stableid,text,bbox[x, y, w, h]normalized 0..1 origin top-left, font/size/color, asemanticlabel when detected (email,phone,iban,date,amount,total,doc_number,signature_label...), itssource(textorocr) andconfidence.regions— logical zones (header,footer,line_items_table,totals,signature_area) with the element ids they group.
Scanned or flattened pages are OCRed automatically (ocr: "auto"); OCR
elements also carry bg, the sampled background colour used for
mask-and-redraw edits.
The coordinate contract
Perceive's bbox values are in the exact frame the action endpoints
accept: redact regions and annotate objects use
the same normalized, top-left-origin coordinates. No conversion needed.
Act on what you perceived
Perceive is step 1 of the perceive → act loop. The action endpoints:
Edit text in place — POST /v1/pdf/edit-text rewrites spans by id; the
design stays intact:
{ "pdf": "<base64>", "edits": [ { "id": "0:12", "text": "New value" } ] }Each edit accepts text, color, size, delete: true; echo the
element's member_ids (merged fragments are blanked server-side). Returns
the edited PDF. The SDK method is client.pdf.editText(pdf, edits).
Burn objects — POST /v1/pdf/annotate stamps editor-style objects
(text, image/signature, rect...) at normalized coordinates and returns the
PDF (X-Burned-Objects header). SDK: client.pdf.annotate(pdf, objects).
Restamp — POST /v1/pdf/import replaces matching text across the
document ({"replacements": [{"old": "...", "new": "..."}]}) — same design,
new data (X-Replaced-Objects header). SDK: client.pdf.restamp(pdf, replacements).
Lower-level reads also exist: POST /v1/pdf/extract-text (raw per-span JSON)
and POST /v1/pdf/ocr (OCR spans with confidence and background colour) —
perceive wraps both with semantics on top.
Next steps
- True redaction — perceive-driven removal.
- MCP server — the same loop packaged for AI agents.
- Import a PDF — turn perceived spans into template variables.