ocr-software: Python client and command line for the ocr-software.com OCR API (MIT, standard library only, Python 3.9 or newer).

Install:
  pipx install ocr-software        command line only, in its own environment
  pip install ocr-software         client library, inside a venv
  uvx ocr-software --version       run once without installing (uv)
First call (list the pipelines, then read one file):
  ocr-software --list-pipelines
  ocr-software scan.pdf --pipeline scan-ctc@2 -o scan.txt

Settings (environment; the command line flag wins):
  OCR_SOFTWARE_API_KEY        API key (starts with ocr_). OCR_KEY is read too, it is the name used in the API docs.
  OCR_SOFTWARE_BASE_URL       default https://api.ocr-software.com ; own server e.g. http://127.0.0.1:8080 (also --base-url). OCR_BASE_URL is read too.
  OCR_SOFTWARE_ALLOWED_DIR    optional: read documents only from below this folder (symlinks and .. do not escape)
The key is never printed and never part of an error message. Without --pipeline the default is used: the fastest and cheapest available pipeline (no previews). It is named on stderr; --list-pipelines marks it. In Python: client.ocr(path, client.default_pipeline()).

Command line:
  ocr-software --list-pipelines                       available pipelines, one per line: name@version, tab, what it delivers and costs; the default one on stderr (no key needed)
  ocr-software scan.pdf --pipeline NAME@VERSION --dry-run   page count and price in euros, nothing runs, nothing is billed
  ocr-software scan.pdf                                text to stdout, with the default pipeline (named on stderr)
  ocr-software scan.pdf --pipeline NAME@VERSION       text to stdout
  ocr-software scan.pdf -o scan.md --pipeline NAME@VERSION --format md
  ocr-software scan.pdf -o scan.pdf.txt --pipeline NAME@VERSION
  ocr-software scan.pdf -o searchable.pdf --pipeline NAME@VERSION --format pdf      searchable PDF, -o is required
  ocr-software scan.pdf --pipeline NAME@VERSION --format json > scan.json
  ocr-software scan.pdf --pipeline NAME@VERSION --base-url http://10.0.0.5:8080     your own server
  python -m ocr_software ...                          the same command without the script on PATH
Options: --format text (default) | md | json | hocr | pdf ; --dpi 150|200|300 (PDF render resolution) ;
  --timeout SECONDS (wait for the job, default 3600) ; --job-budget MILLICENTS (cap; ignored where billing is off) ; --dry-run ; --version
Without -o the result goes to stdout as UTF-8 (not for pdf). With -o the file appears whole or not at all, an existing
file is replaced, and the input file itself is refused as output.

Exit codes:
  0  done, every page read
  1  error from the service or the network (message on stderr with the API error code, for example 401 unauthorized);
     also an address that does not answer: a typo and an outage look the same, so a script may retry this
  2  wrong call: unreadable or unsupported input, output not writable, no key, malformed address (not http/https)
  3  the job is done but a page failed or did not run, or the job stopped early: the result is written anyway,
     stderr says what is missing and names the job; treat the file as incomplete
  4  the job did not finish within --timeout; it keeps running, stderr names the job

Python:
  from ocr_software import Client, PagesUnread
  client = Client()                                    # key and address from the environment
  text = client.ocr("scan.pdf", "NAME@VERSION", "md")  # bytes; raises PagesUnread (with .data) if a page was not read
  pdf = client.ocr("scan.pdf", "NAME@VERSION", "pdf")  # searchable PDF
  job = client.submit("scan.pdf", "NAME@VERSION")      # step by step: submit, wait, result
  client.wait(job["job_id"]); client.result(job["job_id"], "json")
  client.quote("scan.pdf", "NAME@VERSION")             # dry run: page count and price, nothing runs
  from ocr_software import price_sentence; price_sentence(q)   # the same sentence as --dry-run, in euros
Price: the API counts money in millicents, 100,000 millicents = 1 EUR (a page of the cheapest pipeline costs 100 = EUR 0.001).
Today nothing is charged ("you pay EUR 0.00 today"); the list price is shown for information. Free use has limits:
https://ocr-software.com/legal/fair-usage/ ; for more volume write to info@ocr-software.com.
Word boxes (json): box = [x0, y0, x1, y1] in pixels of the page image, origin top left; width, height and dpi of the page say which
image. In PDF points: pixels * 72 / dpi. Per word, verified is true only where a second reader confirmed it (scan-vlm-ctc@2);
safe_ok is always false today. Judge by confidence (0 to 1). Every field: https://ocr-software.com/docs/reference/
Retries follow the API rules: 429, 500, 502, 503, 504 and network errors up to 5 times (waits 1, 2, 4, 8, 16 s or Retry-After). An
upload the service cuts off (it does that beyond 2 uploads at once per key, instead of answering 429) counts like 429. If no connection
can be made at all (wrong address, host down) it gives up after 2 retries and names the address: a few seconds, up to about 10 s on
Windows, where a refused port itself takes 2 s per try (connect timeout 10 s). A Retry-After over 60 s (locked_out) is not waited for. A submit carries an Idempotency-Key, so a retry never creates a second job.
Every document is checked locally first: PDF, PNG, JPEG or TIFF by its first bytes, at most 100 MiB.

Paperless-ngx, pre-consume script (sketch, not tried against a real Paperless; a non-PDF input keeps its file name but becomes a PDF):
  #!/bin/sh
  ocr-software "$DOCUMENT_WORKING_PATH" --pipeline NAME@VERSION --format pdf -o "$DOCUMENT_WORKING_PATH.ocr" && mv "$DOCUMENT_WORKING_PATH.ocr" "$DOCUMENT_WORKING_PATH"
  with OCR_SOFTWARE_API_KEY and OCR_SOFTWARE_BASE_URL set in the environment of the Paperless container.

Tests (fake local service, no network, no key):
  python -m venv .venv
  .venv/bin/pip install pytest             (Windows: .venv\Scripts\pip install pytest)
  .venv/bin/python -m pytest -p no:cacheprovider .      (Windows: .venv\Scripts\python -m pytest -p no:cacheprovider .)
