Learn
Command Line: xipdf-cli
xipdf-cli is XiKey PDF without the window. It reads, renders, searches, edits, splits, checks and OCRs PDF files from a terminal, a script, a server or a CI job, using the same engine as the app. It never opens a window, needs no display and sends nothing to the internet.
Every example on this page was run on Windows x64 with the current XiKey PDF 0.8.0 build, against the fictional demo files that XiKey made for this guide: an Arabic annual report for a date-palm research centre (annual-report-ar.pdf, 6 pages with charts), a one-page scan of it (annual-report-ar-scanned.pdf), a scanned library notice (ar-scanned.pdf), a bilingual brochure (brochure-bilingual.pdf) and an Arabic/English VAT invoice (invoice-ar.pdf). Output is shown exactly as printed, shortened with "…" where it is long. In XiKey PDF 0.8.0 r1, help text and error messages may name the program differently; the commands and options are the same.
When to use it
- Batch OCR of Arabic scans: turn a folder of scanned letters, invoices or archive pages into searchable PDFs overnight.
- Text for search and indexing: pull the text of every PDF into
.txtfiles for a search engine, a database or an AI pipeline. - Servers and scripts: split, combine, extract, rotate and retitle documents as part of another program.
- CI and release checks: fail a build when a PDF cannot be opened or rendered, or when a "redaction" is only a black box over text that is still there.
- Automation and AI agents: call any of the app's automation tools (
run), run a JSON script of steps (run --script), serve the tools to an AI assistant over MCP (mcp), or drive a running app window (ui).
Where it is after installation
| Platform | Package | Where xipdf-cli is |
|---|---|---|
| Windows x64 | MSI installer (the file XiKey Launcher installs) | C:\Program Files\XiKey PDF\xipdf-cli.exe, next to XiKeyPDF.exe |
| Windows x64 | Portable zip XiKey-PDF-<version>-windows-x64-portable.zip |
In the unzipped folder, next to XiKeyPDF.exe |
| macOS | Separate zip XiKey-PDF-cli-<version>-macos-<arch>.zip |
xipdf-cli at the top of the unzipped folder |
| Linux | DEB or RPM package | /usr/bin/xipdf-cli |
| Linux | XiKey-PDF-cli-<version>-linux-<arch>.tar.gz |
xipdf-cli at the top of the unpacked folder |
| Linux | AppImage | Inside the image at usr/bin/xipdf-cli; running the AppImage itself starts the app |
What is published today: the beta channel on download.xikey.com carries XiKey PDF 0.8.0 r1 as the Windows x64 MSI, the macOS x64 and arm64 DMGs and the Linux x64 AppImage. The portable zip, the DEB and RPM packages and the two CLI-only archives are produced by the release recipe but are not on the beta channel yet. The macOS app bundle in the DMG does not contain the command-line tool; on macOS it comes only in the CLI zip.
The Windows installer does not add its folder to PATH. Call the tool by its full path, or add the folder for the current session:
& "C:\Program Files\XiKey PDF\xipdf-cli.exe" --version
$env:PATH = "C:\Program Files\XiKey PDF;$env:PATH" # this PowerShell session only
xipdf-cli --version
Behaviour common to every command
- Exit codes.
0means success and1means failure; there are no other codes. Some commands fail on purpose so that scripts can react:auditexits1when it finds a faux redaction,batchexits1when any file failed,textexits1when any page could not be read (after printing the pages it could).checkis the exception: once it has run it exits0even when files fail, so read its JSON report (see the CI recipe below). - Errors go to standard error as one line prefixed with the program name, for example
xipdf-cli: the password is incorrect. Standard output carries only results, so it is safe to redirect or parse. - Output formats.
info,tools,run,batchanduiprint indented JSON.ocrandauditprint one line of JSON.textprints plain UTF-8 text.splitprints the files it wrote, one per line.render,edit,combineandextractprint nothing on success (renderreports on standard error). Closing the output early, as inxipdf-cli tools | head -1, still exits0. - Options are checked before any file is read. A misspelled option fails at once instead of being ignored:
xipdf-cli info samples/invoice-ar.pdf --pasword xgivesxipdf-cli: info: unknown option --pasword. - Page numbers start at 1. Lists are comma-separated (
1,3,5); ranges such as2-4are refused withbad page number "2-4". - Passwords.
info,text,render,ocr,edit,extract,splitandauditaccept--password PW(the open password, or the owner password where permissions matter).combinehas no password option. A password typed on the command line can end up in shell history and process lists; prefer reading it from a protected variable in scripts. - Existing files.
render,edit,combine,extractandsplitoverwrite their output.ocrrefuses to overwrite (ocr: output already exists; choose a new file).batchnever replaces a file; it adds a number to the name instead. - Help.
xipdf-cli --help(also-h,help, orxipdf-cli <command> --help) prints the full usage and exits0. Runningxipdf-cliwith no command prints the usage as an error and exits1.
Commands
info: what is in a document
xipdf-cli info <file.pdf> [--password PW]
Prints a JSON summary: PDF version, file size, page count, the first page's size in points, title, author, producer and creator, whether the file is encrypted or tagged, JavaScript, XFA forms, and counts of bookmarks, annotations, form fields, links, layers and attachments, plus the first eight page labels and any warnings.
$ xipdf-cli info samples/annual-report-ar.pdf
{
"annotations": 0,
"attachments": 0,
"author": null,
"bookmarks": 0,
"creator": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) … Edg/155.0.0.0",
"encrypted": false,
"fields": 0,
"file": "samples/annual-report-ar.pdf",
"file_size": 1658510,
"first_page_pt": [
594.9599609375,
841.9199829101562
],
"javascript": false,
"layers": 0,
"links": 0,
"page_labels": [
"1",
"2",
"3",
"4",
"5",
"6"
],
"pages": 6,
"pdf_version": "1.4",
"producer": "Skia/PDF m155",
"tagged": true,
"title": "مركز نخلة الواحة لأبحاث التمور — التقرير السنوي ٢٠٢٥",
"warnings": [],
"xfa": null,
"xfa_layout": null
}
Exit code 0. A page of 595 × 842 points is A4.
text: extract the text
xipdf-cli text <file.pdf> [--page N] [--password PW]
Prints the text in reading order. Without --page it prints every page, with a form-feed character (U+000C) on its own line between pages. Arabic comes out in logical order, ready to search or paste.
$ xipdf-cli text samples/annual-report-ar.pdf --page 2
مركز نخلة الواحة لأبحاث التمور
الملخص التنفيذي
EXECUTIVE SUMMARY · ٠١
الملخص التنفيذي
شهد عام ٢٠٢٥ تحوّلًا نوعيًا في عمل المركز؛ إذ انتقلنا من التجارب الحقلية المحدودة إلى برامج إنتاج واسعة تخدم المزارعين في أربع مناطق
زراعية، وركّزنا جهودنا على ثلاثة محاور: تحسين الأصناف، وترشيد مياه الري، ونقل المعرفة إلى المزارع الصغيرة.
٢٤٠ ألف
٢٢٪
…
Exit code 0. A page that does not exist fails:
$ xipdf-cli text samples/annual-report-ar.pdf --page 9
page 9: page 9 does not exist
xipdf-cli: text: page(s) 9 could not be read
Exit code 1. A scanned page has no text layer, so text prints an empty line for it; run ocr first. If a document's permissions forbid copying, text refuses and asks for the owner password with --password.
render: a page as an image
xipdf-cli render <file.pdf> --out FILE [--page N] [--dpi 96] [--password PW]
| Option | Meaning | Default |
|---|---|---|
--out |
Output image; the extension picks the format: .png, .jpg/.jpeg (quality 90), .tif/.tiff, or .pam (raw RGBA) |
required |
--page |
Page to render | 1 |
--dpi |
Resolution in dots per inch | 96 |
$ xipdf-cli render samples/annual-report-ar.pdf --page 3 --dpi 150 --out out/report-p3.png
rendered page 3 at 150 dpi: 1240×1754 px in 13 ms
Exit code 0. The PNG is 1240 × 1754 pixels and 253,593 bytes; the Arabic text, the bar chart and the Arabic-Indic digits render as on screen. At the default 96 dpi, xipdf-cli render samples/invoice-ar.pdf --out out/invoice.jpg gives a 794 × 1123 pixel JPEG of 202,533 bytes. Other extensions are refused (render: --out out/invoice.bmp: use a .png, .jpg, .tif or .pam name), and so is a resolution beyond the renderer's limit: 2000 dpi on an A4 page fails with … exceeds renderer limits (maximum scale 9.730141), which is about 700 dpi.
ocr: make scans searchable
xipdf-cli ocr <in.pdf> --out out.pdf [--lang ara+eng] [--dpi 300] [--pages 1,3] [--all-pages] [--password PW]
| Option | Meaning | Default |
|---|---|---|
--out |
New searchable PDF; must not exist yet | required |
--lang (or --language) |
Tesseract languages: ara, eng or ara+eng |
your default from Preferences → OCR, otherwise eng |
--dpi |
Resolution pages are read at: 200, 300 or 400 |
Preferences → OCR, otherwise 300 |
--pages |
Only these pages | all pages |
--all-pages |
Also read pages that already have reliable text | off |
OCR adds an invisible text layer over the unchanged page image. The output is written to a temporary file and moved into place only when complete. For Arabic documents, always pass --lang ara+eng.
$ xipdf-cli ocr samples/ar-scanned.pdf --lang ara+eng --out out/ar-searchable.pdf
{"growth_percent":2.5210255990505193,"input_bytes":146607,"output":"out/ar-searchable.pdf","output_bytes":150303,"pages":[{"low_confidence":false,"mean_confidence":89.24462890625,"page":1,"skipped":null,"words":56}],"seconds":1.0661024000000001,"seconds_per_page":1.0661024000000001,"words":56}
Exit code 0: 56 words in about a second, mean confidence 89 out of 100, and the file grew by 2.5 %. The result now has Arabic text:
$ xipdf-cli text out/ar-searchable.pdf
مواعيد مكتبة الحى
تفتح المكتبة أبوابها من الساعة التاسعة صباحا حتى الخامسة elas
ويمكن للأعضاء استعارة BW كتب Baa) أسبوعين.
قاعة المطالعة هادئة ومكيفة, وتتوفر فيها خدمة Wi-Fi مجانية, ولا
يسمح بإدخال الطعام إليها.
آخر موعد لإعادة الكتب قبل الإغلاق بنصف ساعة.
…
Recognition is good but not perfect: in this scan a few words came out as Latin letters (elas, BW, Baa)). Check names, numbers and dates in anything important.
The JSON reports, per page: words, mean_confidence (0 to 100), low_confidence (true when the mean is below 70 or a number was read with confidence below 60, a sign to check the page), and skipped with the reason when a page was left alone. The one-page scan of the annual report, which mixes charts and text, gives 312 words with "low_confidence":true and a mean confidence of 73.5: usable for search, but worth checking.
What the language choice does:
$ xipdf-cli ocr samples/ar-scanned.pdf --out out/ar-default.pdf
{… "pages":[{"low_confidence":true,"mean_confidence":29.60999298095703,"page":1,"skipped":null,"words":60}] …}
$ xipdf-cli text out/ar-default.pdf
> cso seglo
elas duaBl Lis loo deli] dell yo Lgoolldeisel] pay
…
Without --lang and without a default language in Preferences, the command line reads English (eng), and an Arabic page comes out as Latin letters with a confidence of 30. --lang ara alone works for pure Arabic pages; ara+eng also keeps English words, codes and Latin digits.
edit: rotate, delete, move, insert, retitle
xipdf-cli edit <in.pdf> --out out.pdf [--rotate PAGES:DEGREES] [--delete PAGES] [--move PAGES:TO]
[--insert-blank N] [--title T] [--author A] [--full] [--password PW]
| Option | Meaning |
|---|---|
--rotate 1,3:90 |
Rotate pages clockwise by a multiple of 90 degrees |
--delete 2,4 |
Delete pages |
--move 5:1 |
Move pages so they start at position 1 |
--insert-blank 1 |
Insert a blank US Letter page (612 × 792 points) so it becomes page 1 |
--title, --author |
Set the document title and author |
--full |
Write a complete new file instead of appending the changes to the original bytes (the default) |
Options run in the order you give them, and each page number refers to the document as the earlier options left it. Options can repeat.
$ xipdf-cli edit samples/annual-report-ar.pdf --out out/report-edited.pdf --rotate 3:90 --delete 6 --move 5:1 --insert-blank 1 --title "التقرير السنوي ٢٠٢٥ — نسخة المراجعة" --author "قسم الاتصال المؤسسي"
$ xipdf-cli info out/report-edited.pdf
…
"author": "قسم الاتصال المؤسسي",
…
"pages": 6,
…
"title": "التقرير السنوي ٢٠٢٥ — نسخة المراجعة",
…
Both exit 0. Step by step: page 3 is rotated; page 6 (the outlook) is deleted, leaving 5 pages; page 5 (the programmes chapter) moves to the front; a blank page is inserted before everything. Page 1 is now blank and page 2 starts with البرامج والمشروعات. The default save appends to the original (1,667,248 bytes); --full rewrites the file (1,456,269 bytes for a retitled copy).
Mistakes fail before anything is written:
$ xipdf-cli edit samples/annual-report-ar.pdf --out out/x.pdf --rotat 3:90
xipdf-cli: edit: unknown option "--rotat" (expected one of: --out, --password, --full, --rotate, --delete, --move, --insert-blank, --title, --author)
$ xipdf-cli edit samples/annual-report-ar.pdf --out out/x.pdf --delete 7
xipdf-cli: --delete: page 7 does not exist; after the earlier options the document has 6 pages
$ xipdf-cli edit samples/invoice-ar.pdf --out out/x.pdf --rotate 1:45
xipdf-cli: rotation must be a multiple of 90 degrees
combine: join files
xipdf-cli combine <a.pdf> <b.pdf> … --out combined.pdf
Joins two or more PDFs in the order given. Each source file gets a top-level bookmark named after it; links, form fields, layers and attachments come along.
$ xipdf-cli combine samples/invoice-ar.pdf samples/brochure-bilingual.pdf samples/annual-report-ar.pdf --out out/combined.pdf
$ xipdf-cli info out/combined.pdf
…
"bookmarks": 3,
…
"pages": 9,
…
Exit code 0: 1 + 2 + 6 pages and three bookmarks. A single input fails with combine: give at least two input files.
extract: copy pages into a new file
xipdf-cli extract <in.pdf> --pages 1,3,5 --out out.pdf [--password PW]
$ xipdf-cli extract samples/annual-report-ar.pdf --pages 1,3,5 --out out/report-highlights.pdf
Exit code 0; the new file has 3 pages. Pages keep the order you list, so --pages 6,1 puts the outlook before the cover.
split: one file into several
xipdf-cli split <in.pdf> (--every N | --before PAGES) [--out-dir DIR] [--password PW]
--every N starts a new part every N pages; --before 3,7 starts a new part at each listed page. Give exactly one of them. Parts are named <name>-p<first>-<last>.pdf (or -p<n>.pdf for one page) in --out-dir (default: the current folder, created if missing), and each written path is printed.
$ xipdf-cli split samples/annual-report-ar.pdf --every 2 --out-dir out/split-every
out/split-every\annual-report-ar-p1-2.pdf
out/split-every\annual-report-ar-p3-4.pdf
out/split-every\annual-report-ar-p5-6.pdf
$ xipdf-cli split samples/annual-report-ar.pdf --before 2,6 --out-dir out/split-chapters
out/split-chapters\annual-report-ar-p1.pdf
out/split-chapters\annual-report-ar-p2-5.pdf
out/split-chapters\annual-report-ar-p6.pdf
Both exit 0. Giving both options fails with split: give either --every N or --before PAGES.
check: can every file be opened and rendered?
xipdf-cli check <files or folders…> [--timeout 20] [--dpi 36] [--json report.json]
Finds every .pdf in the files and folders given (folders are searched recursively), then opens, inspects and fully renders each one (up to 500 pages, both pixels and text) in a separate process with a time limit, so a file that hangs or crashes is reported instead of stopping the run.
| Option | Meaning | Default |
|---|---|---|
--timeout |
Seconds allowed per file | 20 |
--dpi |
Render resolution for the check | 36 |
--json |
Write the full report to this file | none |
Problems and a summary go to standard error:
$ xipdf-cli check samples
checked 5 files: {"ok": 5}
$ xipdf-cli check out/ci --json out/ci-check.json
[1/2] open-error:the-file-is-not out/ci\broken.pdf the file is not a readable PDF: no readable page tree or cross-reference data was found
checked 2 files: {"ok": 1, "open-error": 1}
Both exit 0, even the second (here broken.pdf is a text file renamed to .pdf). The report lists every file:
[
{
"detail": "the file is not a readable PDF: no readable page tree or cross-reference data was found",
"failed_pages": 0,
"file": "out/ci\\broken.pdf",
"ms": 0,
"pages": 0,
"status": "open-error:the-file-is-not",
"warnings": 0
},
{
"detail": "",
"failed_pages": 0,
"file": "out/ci\\invoice-ar.pdf",
"ms": 12,
"pages": 1,
"status": "ok",
"warnings": 0
}
]
status is ok, page-errors (some pages failed; detail says which), open-error:<reason>, timeout, crash or harness-error. check exits 1 only when it cannot run at all, for example check: no PDF files found.
audit: find fake redactions
xipdf-cli audit <file.pdf> [--password PW]
A faux redaction hides text only visually: a black rectangle drawn over text that can still be selected, copied or extracted. audit reports two kinds:
covered-text: an opaque filled rectangle, dark enough to make the text unreadable, painted after the text it covers;unapplied-mark: a redaction mark that was placed but never applied, so the text underneath is untouched.
It prints JSON and exits 1 when it finds anything, so a script or CI job can stop on it. Real redaction, which removes the content, never triggers it. The audit also fails when it cannot read part of the file, rather than reporting "nothing found". Its known blind spots: covers drawn as arbitrary shapes, shadings or patterns, semi-transparent covers, text inside form XObjects, and pages with /Rotate may be missed or misplaced.
A clean file passes:
$ xipdf-cli audit samples/invoice-ar.pdf
{"count":0,"file":"samples/invoice-ar.pdf","findings":[]}
Exit code 0. A copy of the invoice where the seller's VAT number was only marked for redaction (made with the run --script example below, without redact_apply) fails:
$ xipdf-cli audit out/invoice-marked-only.pdf
{"count":2,"file":"out/invoice-marked-only.pdf","findings":[{"covered_text":"VAT 310987654300003","kind":"unapplied-mark","page":1,"rect":[55.2976188659668,763.0709838867188,127.25257873535156,771.0659790039062]},{"covered_text":"310987654300003","kind":"unapplied-mark","page":1,"rect":[427.0898132324219,624.9105224609375,495.1648254394531,633.7080078125]}]}
xipdf-cli: audit: 2 faux redaction(s) found
Exit code 1. The same copy after redact_apply passes with "count":0.
batch: convert, OCR and optimize a whole folder
xipdf-cli batch <files or folders…> --steps convert,ocr,optimize --out-dir DIR
[--recursive] [--root DIR] [--stop-on-error]
[--ppi 150] [--quality 60] [--dpi 300] [--language en] [--ocr-all-pages]
Runs one to three steps on every file, one file at a time, and writes each result into --out-dir with the same relative path and a .pdf extension. The sources are never changed and an existing output is never replaced (a numbered name such as report (2).pdf is used instead).
| Step | What it does | Its options |
|---|---|---|
convert |
Turns images (png, jpg, jpeg, tif, tiff, gif, bmp, jp2, j2k, jpx) and text files (txt, text) into PDF. Must be the first step. Without it, only PDFs are processed and other files are skipped. |
none |
ocr |
Adds a text layer with the built-in English (Latin-script) recognizer | --dpi (72–600, default 300), --language (only en), --ocr-all-pages |
optimize |
Reduce File Size: downsamples images above 1.5 × the target and recompresses them as JPEG; keeps the original bytes if the result is not smaller | --ppi (9–1600, default 150, so images above 225 ppi), --quality (1–100, default 60) |
Other options: --recursive also descends into subfolders (off by default), --stop-on-error skips the remaining files after the first failure, and --root DIR confines every path to that folder. Discovery is limited to 10,000 entries and 64 folder levels, inputs to 512 MiB, and links and special files are skipped. The command prints a JSON report and exits 1 if any file failed.
$ xipdf-cli batch samples --steps optimize --ppi 100 --quality 50 --out-dir out/batch-small
{
"completed": 5,
"failed": 0,
"files": [
{
"bytes": 69075,
"output": "\\\\?\\V:\\…\\out\\batch-small\\annual-report-ar-scanned.pdf",
"path": "\\\\?\\V:\\…\\samples\\annual-report-ar-scanned.pdf",
"status": "ok",
"steps": [
{
"bytes_after": 69075,
"bytes_before": 330442,
"step": "optimize"
}
]
},
…
],
"skipped": 0,
"stopped": false
}
Exit code 0. The scanned report shrank from 330,442 to 69,075 bytes and the library scan from 146,607 to 27,919. Converting a folder of images works the same way: xipdf-cli batch out/images --steps convert,optimize --out-dir out/batch-converted turned invoice.jpg into invoice.pdf (exit 0).
Important for Arabic: the ocr step of batch uses only the built-in English recognizer. --language ara+eng is refused (unsupported language "ara+eng"), and on an Arabic scan the step reports success while producing Latin-letter noise (283 "words" on the scanned report). For Arabic scans, use the ocr command in a loop, as in the recipe below. The built-in recognizer's models ship with the installed app; without them the step fails with the text recognition models are not installed (reinstall XiKey PDF, …).
Settings that need a missing step are refused, for example --dpi without ocr: dpi/language/skip_text_pages settings need the ocr step.
tools and run: every automation tool from the command line
xipdf-cli tools
xipdf-cli run <tool> [key=value …] [--root DIR] [--out image.png]
xipdf-cli run --script steps.json [--root DIR]
tools prints every automation tool as JSON: name, description, read_only, the app command it matches, and its input_schema (JSON Schema). This build has 163 tools, from doc_open, text_find and page_render to redaction, forms, signatures, comments, accessibility, PDF/A and comparison.
run calls one tool. Each key=value becomes an argument; the value is read as JSON when it can be (pages=[1,3], degrees=90, full=true) and as a string otherwise. A tool that returns an image saves it to --out. Each run is a fresh session, so a single call suits tools that take file paths:
$ xipdf-cli run doc_combine 'paths=["samples/invoice-ar.pdf","samples/brochure-bilingual.pdf"]' out=out/run-combined.pdf
{
"bytes": 231589,
"path": "out/run-combined.pdf"
}
Exit code 0. Tools that work on an open document (doc) need a script, because a document id lives only for one session.
run --script runs a JSON array of steps in one session. Each step is {"tool": …, "args": {…}}, plus "out" for an image. The first opened document is id 1, the next id 2, and so on. This script finds the invoice's tax-number labels, removes both VAT numbers for good, saves a copy and renders it:
[
{"tool": "doc_open", "args": {"path": "samples/invoice-ar.pdf"}},
{"tool": "text_find", "args": {"doc": 1, "query": "الرقم الضريبي"}},
{"tool": "redact_mark", "args": {"doc": 1, "words": ["310987654300003", "300123456700003"]}},
{"tool": "redact_apply", "args": {"doc": 1}},
{"tool": "doc_save", "args": {"doc": 1, "path": "out/invoice-redacted.pdf"}},
{"tool": "page_render", "args": {"doc": 1, "page": 1, "dpi": 72}, "out": "out/invoice-redacted.png"}
]
$ xipdf-cli run --script out/redact-invoice.json
{
"dirty": false,
"doc": 1,
…
"name": "invoice-ar.pdf",
"pages": 1,
…
}
{
"complete": true,
"count": 2,
"matches": [
{
"page": 1,
"rects": [
[
499.8208312988281,
208.2119903564453,
543.5091552734375,
217.00949096679688
]
],
"text": "الرقم الضريبي"
},
…
],
…
}
{
…
"marked": 3,
"marks_pending": 3,
"matched_words": [
{
"marked": 2,
"word": "310987654300003"
},
{
"marked": 1,
"word": "300123456700003"
}
],
…
}
{
"applied": 3,
…
"marks_pending": 0,
…
}
{
"bytes": 75952,
…
"incremental": false,
"path": "out/invoice-redacted.pdf"
}
{"height":842,"image":"out/invoice-redacted.png","width":595}
Exit code 0. Each step prints its result. text on the saved copy no longer contains either number, audit passes, and the rendered page shows black boxes where they were. A failing step stops the script with step N (tool): <message> and exit code 1.
Coordinates in tool arguments and results are in points from the top-left of the page. With --root DIR, every path a tool or step names, "out" included, must be inside DIR, and relative paths resolve inside it:
$ xipdf-cli run doc_combine 'paths=["invoice-ar.pdf","brochure-bilingual.pdf"]' out=../out/rooted.pdf --root samples
xipdf-cli: ../out/rooted.pdf is outside the allowed directory \\?\V:\…\samples
The automation ocr_recognize and ocr_status tools use the built-in English recognizer, like batch. For Arabic OCR, use the ocr command.
mcp: serve the tools to an AI assistant
xipdf-cli mcp [--root DIR] [--compact]
Starts a Model Context Protocol server on standard input and output (JSON lines; protocol versions 2025-06-18, 2025-03-26 and 2024-11-05). It opens no network port, never starts by itself, and stops when its input closes. It offers the same tools as run; --compact lists a core set of 15 (doc_open, doc_info, doc_save, doc_close, page_render, text_extract, text_find, doc_combine, doc_split, edit_undo, command_list, command_run, command_batch, doc_inspect, render_preview) plus tool_search and tool_call to reach the other tools, instead of all 163. Always give --root so the assistant can only touch one folder. A startup line goes to standard error.
The MCP server is in the standard builds (Windows, macOS and Linux). A build made without the mcp feature answers mcp: this build leaves out the MCP server.
A typical client configuration:
{
"mcpServers": {
"xikey-pdf": {
"command": "C:\\Program Files\\XiKey PDF\\xipdf-cli.exe",
"args": ["mcp", "--root", "D:\\Documents\\PDF", "--compact"]
}
}
}
Tested exchange (--root samples --compact): after initialize and tools/list (17 tools), the call doc_open {"path": "invoice-ar.pdf"} returned document 1, and text_find {"doc": 1, "query": "فاتورة ضريبية"} returned one match on page 1. Exit code 0 when the input closed.
ui: drive a running app window
xipdf-cli ui --control FILE <method> [key=value …] [--out shot.png]
ui talks to an XiKey PDF window that was started with --control FILE. The app then listens on a random port on 127.0.0.1 only, and writes the port and a random token to FILE; every connection must present the token. Keep that file in a folder only you can write. The command refuses a control file that is a link or not a regular file, and on Linux and macOS also one owned by another user or open to other users.
# Windows: start the app with a control file
& "C:\Program Files\XiKey PDF\XiKeyPDF.exe" --control "$env:LOCALAPPDATA\xipdf-control.json" samples\annual-report-ar.pdf
# then, from another terminal
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" state
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" screenshot --out window.png
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" set key=language value=ar
xipdf-cli ui --control "$env:LOCALAPPDATA\xipdf-control.json" quit
On Linux the app is xipdf; on macOS it is "XiKey PDF.app/Contents/MacOS/XiKeyPDF".
Not run for this guide. The window-control examples above were not executed: the test machine's XiKey PDF window was in use by another test at the time. They are written from the app's control-channel reference and are not a recorded transcript. What was run is the error handling: with no running app, ui --control FILE state exits 1 with can't reach the app on port …: … (is it still running?), and with no method it lists them.
Methods (the ui. prefix is optional; parameters are key=value, read as JSON when possible):
| Method | Does |
|---|---|
state |
Open documents, active document, page, zoom, mode, panels, dialog, window size |
inspect query=… role=… limit=… |
Widgets with id, role, label, value, position and state |
commands, command id=… |
List the app's commands, or run one as the menu would |
open path=… |
Open a file |
set key=… value=… |
View options and preferences, such as language, theme (light, dark, system), panel, select, default-mode |
click id=… / label=… / x=… y=…, focus |
Click or focus a widget or a point |
type text=…, key key=… modifiers=… |
Keyboard input |
move, drag from=[x,y] to=[x,y] |
Pointer movement |
resize width=… height=… |
Window size (320–8192 points) |
screenshot (with --out shot.png) |
PNG of the window; without --out the image is returned as base64 |
quit |
Close normally; with unsaved changes the app asks first instead of quitting |
Each request waits up to 40 seconds. Input and screenshots need the window to be drawn; a minimized or hidden window answers with an error asking you to bring it to the front.
--version
$ xipdf-cli --version
xipdf-cli 0.8.0
Community: https://xikey.com (help and feedback)
Website: https://desktop.xikey.com/products/xipdf/
Exit code 0.
OCR in detail
- Engine and languages. The
ocrcommand uses Tesseract 5.3.0 with thetessdata_best4.1.0 models for Arabic (ara) and English (eng). Combine languages with+;ara+engis the right choice for Arabic documents that also contain English, codes or Latin digits. Recognition runs entirely on your computer. - Windows x64: Tesseract and both language models are installed with XiKey PDF, in
C:\Program Files\XiKey PDF\ocr\tesseract\. OCR works offline from the first run. - Linux: install Tesseract and the language packs from your distribution. The DEB and RPM packages declare them as dependencies; the AppImage does not include them.
# Debian / Ubuntu
sudo apt install tesseract-ocr tesseract-ocr-ara tesseract-ocr-eng
# Fedora / RHEL
sudo dnf install tesseract tesseract-langpack-ara tesseract-langpack-eng
# openSUSE
sudo zypper install tesseract-ocr tesseract-ocr-traineddata-ara tesseract-ocr-traineddata-eng
- macOS: Tesseract is not bundled. Install it with Homebrew; Arabic is in
tesseract-lang, not in the base formula. Then check thattesseract --list-langslists botharaandeng.
brew install tesseract tesseract-lang
- How Tesseract is found: the path chosen in Preferences → OCR → Locate; then
ocr/tesseract/next toxipdf-cli; then the XiKey PDF OCR folder in your user profile; thenPATH. Language data is read from that user folder'stessdata, from Tesseract's own data folder, and fromTESSDATA_PREFIXwhen set. The user OCR folder is%APPDATA%\XiPDF\ocron Windows and~/.local/share/XiPDF/ocron Linux and macOS ($XDG_DATA_HOME/XiPDF/ocrwhen that is set). - Defaults from the app. The command line reads the default language and resolution you set in the app's Preferences → OCR. With no saved default, it uses
engat 300 dpi. (The app itself picks Arabic + English for scans; the command line does not guess, so pass--lang.) --dpiis the resolution pages are read at: 200, 300 or 400. A scanned page is never read above its own image resolution, because enlarging a scan only blurs the letters.- Pages that already have text. By default,
ocrleaves alone every page whose text layer is reliable and reports it as"skipped": "the page already contains text". Pages whose text is damaged, garbled or missing are recognized even then. If every selected page is skipped, the command fails and writes nothing:
$ xipdf-cli ocr samples/annual-report-ar.pdf --lang ara+eng --out out/report-text-ocr.pdf
xipdf-cli: ocr: no pages recognized: the page already contains text; the page already contains text; …
--all-pagesalso reads pages that have good text, to catch words inside pictures such as chart labels. Words the page already has are not added a second time. On page 3 of the annual report, which has Arabic text and two charts, it added 9 words:
$ xipdf-cli ocr samples/annual-report-ar.pdf --lang ara+eng --pages 3 --all-pages --out out/report-p3-all.pdf
{"growth_percent":0.19149718723432407,"input_bytes":1658510,"output":"out/report-p3-all.pdf","output_bytes":1661686,"pages":[{"low_confidence":true,"mean_confidence":76.6895751953125,"page":3,"skipped":null,"words":9}],"seconds":3.0978103,"seconds_per_page":3.0978103,"words":9}
--pageslimits recognition to the listed pages; a page beyond the end fails withocr: page number is outside this document.
Recipes
Batch-OCR a folder of Arabic scans
Bash (Linux, macOS, or Git Bash on Windows). It skips files already done, so it can be re-run after an interruption, and keeps each page report as JSON:
#!/usr/bin/env bash
# Make every scanned PDF in scans/ searchable, into searchable/.
set -u
mkdir -p searchable
status=0
for f in scans/*.pdf; do
out="searchable/$(basename "$f")"
if [ -e "$out" ]; then echo "skip $f (already done)"; continue; fi
if xipdf-cli ocr "$f" --lang ara+eng --out "$out" > "$out.json"; then
echo "ok $f"
else
echo "FAILED $f" >&2
rm -f "$out.json"
status=1
fi
done
exit $status
$ bash ocr-folder.sh
ok scans/annual-report-ar-scanned.pdf
ok scans/ar-scanned.pdf
$ bash ocr-folder.sh
skip scans/annual-report-ar-scanned.pdf (already done)
skip scans/ar-scanned.pdf (already done)
Both runs exit 0. PowerShell (Windows):
# Make every scanned PDF in scans\ searchable, into searchable-ps\.
New-Item -ItemType Directory -Force searchable-ps | Out-Null
$failed = 0
Get-ChildItem scans -Filter *.pdf | ForEach-Object {
$out = Join-Path searchable-ps $_.Name
if (Test-Path $out) { "skip $($_.Name) (already done)"; return }
xipdf-cli ocr $_.FullName --lang ara+eng --out $out | Set-Content "$out.json"
if ($LASTEXITCODE -eq 0) { "ok $($_.Name)" } else { Write-Warning "FAILED $($_.Name)"; $failed++ }
}
exit [int]($failed -gt 0)
PS> .\ocr-folder.ps1
ok annual-report-ar-scanned.pdf
ok ar-scanned.pdf
Exit code 0. Expect about one to three and a half seconds per page on a desktop computer.
Extract the text of every PDF to .txt
#!/usr/bin/env bash
# Write the text of every PDF in the given folder to text/<name>.txt (UTF-8).
set -u
src="${1:-.}"
mkdir -p text
for f in "$src"/*.pdf; do
name="$(basename "${f%.*}")"
xipdf-cli text "$f" > "text/$name.txt" || echo "FAILED $f" >&2
done
# Write the text of every PDF in the given folder to text-ps\<name>.txt (UTF-8).
param([string]$Source = '.')
# xipdf-cli writes UTF-8; tell PowerShell, so Arabic stays intact whatever the console code page.
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
New-Item -ItemType Directory -Force text-ps | Out-Null
Get-ChildItem $Source -Filter *.pdf | ForEach-Object {
$txt = Join-Path text-ps ($_.BaseName + '.txt')
xipdf-cli text $_.FullName | Set-Content -Encoding UTF8 $txt
if ($LASTEXITCODE -ne 0) { Write-Warning "FAILED $($_.Name)" }
}
Both were run on the samples folder (exit 0). The invoice's text file starts with مشغل السدو للحرف التراثية. The two scans give empty files: OCR them first. Windows PowerShell 5.1 writes UTF-8 with a byte-order mark and CRLF line ends; the bash version writes the tool's output unchanged.
Split a report by chapters
Look up where each chapter starts (for example with xipdf-cli text <file> --page N), then split before those pages. In the annual report, chapters ٠١ to ٠٥ start on pages 2 to 6, after the cover:
$ xipdf-cli split samples/annual-report-ar.pdf --before 2,3,4,5,6 --out-dir chapters
chapters\annual-report-ar-p1.pdf
chapters\annual-report-ar-p2.pdf
chapters\annual-report-ar-p3.pdf
chapters\annual-report-ar-p4.pdf
chapters\annual-report-ar-p5.pdf
chapters\annual-report-ar-p6.pdf
Exit code 0. For a document with bookmarks, the doc_split tool can also split at top-level bookmarks ("bookmarks": true) from a run --script.
A CI gate with check and audit
#!/usr/bin/env bash
# CI gate: fail the build if any PDF in $1 is unreadable or has a faux redaction.
set -u
dir="${1:?usage: pdf-gate.sh DIR}"
fail=0
# 1. Robustness: check always exits 0 once it has run, so read its JSON report.
xipdf-cli check "$dir" --timeout 30 --json check.json || exit 1
python3 - check.json <<'PY' || fail=1
import json, sys
bad = [r for r in json.load(open(sys.argv[1], encoding="utf-8")) if r["status"] != "ok"]
for r in bad:
print(f'check: {r["status"]}: {r["file"]}: {r["detail"]}')
sys.exit(1 if bad else 0)
PY
# 2. Redactions: audit exits 1 when it finds a faux redaction (or cannot read the file).
for f in "$dir"/*.pdf; do
xipdf-cli audit "$f" > /dev/null || fail=1
done
exit $fail
Run on a folder with the redacted invoice and the brochure, the gate passes (exit 0). Run on a folder with the original invoice, the marked-but-not-applied copy and a broken file, it fails (exit 1):
$ bash pdf-gate.sh release-bad
[1/3] open-error:the-file-is-not release-bad\broken.pdf the file is not a readable PDF: …
checked 3 files: {"ok": 2, "open-error": 1}
check: open-error:the-file-is-not: release-bad\broken.pdf: the file is not a readable PDF: …
xipdf-cli: release-bad/broken.pdf: not a PDF file (no %PDF- header)
xipdf-cli: audit: 2 faux redaction(s) found
Troubleshooting
OCR fails with "Tesseract is not installed" or "OCR language … is not installed". The engine or a language pack is missing. On Windows, repair or reinstall XiKey PDF; the engine belongs in ocr\tesseract\ next to xipdf-cli.exe. On Linux, install the distribution packages listed above; on macOS, brew install tesseract tesseract-lang. Then check tesseract --list-langs. Asking for a language that is not installed gives, for example, ocr: no pages recognized: text recognition failed: OCR language fra is not installed; download it in Preferences → OCR.
The built-in recognizer is missing (batch --steps ocr or --lang en fails with the text recognition models are not installed (reinstall XiKey PDF, …)). These models ship in the models folder of the installed app; a copy of xipdf-cli alone does not have them. Reinstall, or run the command from the installed folder.
A password-protected file. Without a password:
$ xipdf-cli info out/invoice-protected.pdf
xipdf-cli: the document is protected by a password
$ xipdf-cli info out/invoice-protected.pdf --password wrong
xipdf-cli: the password is incorrect
$ xipdf-cli text out/invoice-protected.pdf --password demo-open-2025
مشغل السدو للحرف التراثية
…
The first two exit 1, the third 0. If text says the permissions don't allow copying, pass the owner (permissions) password. combine cannot open protected inputs; extract the pages with --password first.
OCR text is Latin gibberish on an Arabic scan. The wrong language was used: without --lang the command line reads English. Re-run with --lang ara+eng, or set Arabic + English as the default in Preferences → OCR. The batch OCR step cannot read Arabic at all; use the ocr command.
Words are wrong even with the right language. Check mean_confidence and low_confidence in the JSON. Clean, straight scans at 300 dpi read best; faint, skewed, handwritten or decorative text produces more errors. Try --dpi 400 only for small print on scans that have that resolution.
Linux: the app does not start, with an error about libxkbcommon-x11. That library is needed by the window only. xipdf-cli does not use the window system, so it runs on servers without a display or keyboard libraries. To run the app, install libxkbcommon-x11-0 (Debian/Ubuntu) or libxkbcommon-x11 (Fedora/RHEL).
Arabic shows as question marks in a Windows console. The tool writes UTF-8. In PowerShell set [Console]::OutputEncoding = [System.Text.Encoding]::UTF8, or redirect to a file and open it in an editor.
Sources
- Command-line tool and automation — 2026-10-16.
- Batch processing — 2026-10-16.
- MCP conventions — 2026-10-16.
- Known limitations — 2026-10-16.