Your OCR pipeline probably uploads the document. It doesn't have to Browser-native OCR eliminates the need to upload documents to third-party servers, according to a technical post about @stabrise/scaledp@0.1.1, which runs PDF rendering, text detection, OCR, and entity recognition entirely in the browser using onnxruntime-web. The post argues that hosted OCR APIs typically cost $0.50 to $1.50 per thousand pages and require data processing agreements, while local inference removes compliance overhead and per-page costs, though it increases first-load bytes. Your OCR pipeline probably uploads the document. It doesn't have to Written 2026-09-03. Code examples target @stabrise/scaledp@0.1.1. No prior OCR or ONNX knowledge assumed. TL;DR: Every mainstream OCR API has the same architecture — your document goes over the wire to someone else's disk. Browser-native inference removes that box entirely: @stabrise/scaledp runs PD Written 2026-09-03. Code examples target @stabrise/scaledp@0.1.1. No prior OCR or ONNX knowledge assumed. TL;DR: Every mainstream OCR API has the same architecture — your document goes over the wire to someone else's disk. Browser-native inference removes that box entirely: @stabrise/scaledp runs PDF rendering, text detection, OCR and entity recognition on onnxruntime-web, in the tab, with no upload. That is a real win for sensitive documents and a real cost in first-load bytes. This post is about both. Draw the data flow of any hosted document API and you get three boxes: your app --HTTPS-- their API -- their storage + GPUs The third box is the one that generates paperwork. It is why OCR procurement involves a data processing agreement, a retention policy, a sub-processor list, and a conversation with whoever owns compliance. The engineering is five lines; the approval is five weeks. That box also has a price. Hosted OCR is billed per page — typically somewhere between $0.50 and $1.50 per thousand pages, plus egress. For a product that OCRs every uploaded file, that is a line item that grows exactly as fast as your usage does. None of this is a criticism of hosted APIs. For 10,000 pages a night they are the correct answer, and I say so in the trade-offs below. The point is narrower: the third box is not a law of physics. The models are small enough to run in a browser, and onnxruntime-web is good enough to run them. your app -- WASM/WebGPU in the tab Here is a complete, runnable pipeline. It renders a PDF, reads it with PaddleOCR, and pulls out entities — and it makes no request to any server of ours, because there isn't one. import { Pipeline, configure } from '@stabrise/scaledp' import { PdfToImage } from '@stabrise/scaledp/pdf' import { PaddleTextRecognizer } from '@stabrise/scaledp/ocr' import { GlinerNer } from '@stabrise/scaledp/ner' configure { cache: 'indexeddb', pdf: { workerSrc: '/pdf.worker.min.mjs' }, onProgress: { file, loaded, total } = { console.log ${file}: ${Math.round loaded / total 100 }% }, } const pipeline = new Pipeline new PdfToImage { resolution: 300 } , new PaddleTextRecognizer { keepFormatting: true } , new GlinerNer { labels: 'person', 'organization', 'email', 'phone' } , const rows = await pipeline.transform file for const row of rows { console.log page ${row.page}: , row.text.text.slice 0, 80 for const entity of row.ner.entities { console.log ' ', entity.entity group, '=', entity.word } } page 1: INVOICE 2024-0417 Acme Industries GmbH Rechnungsdatum 14.03.2024 … organization = Acme Industries GmbH person = Marta Feldmann email = accounts@acme-industries.de One row per page. Each stage reads a named field on the row and writes another one — PdfToImage writes image, PaddleTextRecognizer reads it and writes text, GlinerNer reads that and writes ner. That is the whole composition model, and it is the same one the Python ScaleDP library uses on Spark. The DPA. Not "is easier to get" — is not needed for this data flow. There is no processor, because there is no third party in the path. The retention question. "How long do you keep it?" has an answer that requires no policy document: the bytes were in a tab, and the tab closed. Egress and per-page cost. After the first load, the marginal cost of a page is CPU time on a machine you are not paying for. Round-trip latency on re-runs. This is the underrated one. Changing a threshold and re-running is 200 ms, not a network round trip, which changes what kind of UI you can build. An interactive parameter panel over a hosted API is a rate-limiting problem; over a local pipeline it is just a re-render. Bytes on first load. PaddleOCR's default preset v6-small is about 6 MB of weights — genuinely small. The default GLiNER NER model is ~580 MB. That is not a rounding error, and it is the single biggest objection to this whole approach. Mitigations exist — smaller models gliner-pii-edge is ~181 MB , IndexedDB caching so it downloads once, self-hosting the weights from your own origin — but the first visit pays. A device you do not control. Your server has known cores and known memory. Your user's machine has neither. WebGPU may be available or may not; threads need response headers your host may not send. The library reports what it actually got rather than assuming: import { isWebGpuAvailable, isCrossOriginIsolated } from '@stabrise/scaledp/ocr' console.log { webgpu: await isWebGpuAvailable , // false on plenty of real machines threads: isCrossOriginIsolated , // false unless COOP/COEP are set } Throughput. One tab is one machine. There is no horizontal scaling story here beyond "open more tabs", and that is not a story. At volume, a server wins and it is not close. If you are processing 10,000 pages a night, the per-page cost of a GPU you already own beats the per-page cost of downloading 580 MB to ten thousand browsers. Use the Python sibling; the pipeline code ports almost mechanically. The 580 MB is real and unavoidable if you want good multilingual NER. You can drop to a 181 MB model, drop NER entirely and keep OCR at 6 MB, or self-host from a fast origin — but you cannot make a good zero-shot NER model small. First run is much slower than the tenth. Model download dominates the first pipeline run completely. Design the UI around that onProgress, a cache check before you start rather than hoping users are patient. AGPL-3.0-or-later. Same licence as the Python library. If that does not work for a closed-source product, StabRise does commercial licensing. Worth knowing before you build on it, not after. npm install @stabrise/scaledp onnxruntime-web ppu-paddle-ocr pdfjs-dist The engines are optional peer dependencies — install only the ones your pipeline uses. Importing the core pulls in no ML runtime at all. Drop your own PDF into the builder at scaledp-ts.stabrise.com/demo, pick a detector and a recognizer, and watch the network panel while it runs. After the models are cached you can turn the wifi off and run it again. Repo: StabRise/scaledp-ts Quickstart Models and caching — sizes, self-hosting, private repos Execution providers — WebGPU, threads, and what your host has to send Python sibling: StabRise/ScaleDP Key Takeaways - •Written 2026-09-03 - •This story was reported by Dev.to , covering developments in the dev space. - •AI advancements continue to reshape industries — read the full article on Dev.to for complete coverage. 📖 Continue reading the full article: Read Full Article on Dev.to → https://dev.to/mykola melnyk ml/your-ocr-pipeline-probably-uploads-the-document-it-doesnt-have-to-5c9d