Key takeaways
- /document/ocr currently accepts PDFs only; run /document/convert first for Word, PowerPoint, or image-based documents.
- Use /image/ocr for photographs, screenshots, and camera captures.
- The meta format keeps the text on the file object so later Steps can filter or burn it in without a second pass.
Text extraction becomes useful when it is part of the pipeline that already handles the upload, rather than a separate service that has to be told where the file is. The main decisions are which Robot matches the input, and what shape the output needs to take downstream.
What matters most
- The list granularity returns positioned fragments, while full returns one block of text.
- Providers can be pinned to aws or gcp when consistency matters more than availability.
Route each input to the Robot that accepts it
Text extraction fails most often at the routing step rather than at recognition. /document/ocr currently accepts PDFs only, so a Word file, a PowerPoint deck, or a TIFF sent straight to it will not produce text. Convert those with /document/convert first, and send photographs and screenshots to /image/ocr instead. A single /file/filter Step in front of the pipeline is usually enough to split the traffic by type.
Both Robots take the same three parameters. provider selects between AWS and GCP, and defaults to automatic selection. granularity chooses between one block of text and a positioned list of fragments. format decides whether the text comes back as a file or is attached to the file object for later Steps to read.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"pdfs": {
"use": ":original",
"robot": "/file/filter",
"accepts": [
["${file.mime}", "regex", "application/pdf"]
]
},
"photos": {
"use": ":original",
"robot": "/file/filter",
"accepts": [
["${file.mime}", "regex", "^image/"]
]
},
"deskewed": {
"use": "pdfs",
"robot": "/document/autorotate"
},
"pdf_text": {
"use": "deskewed",
"robot": "/document/ocr",
"format": "json",
"granularity": "full"
},
"photo_text": {
"use": "photos",
"robot": "/image/ocr",
"format": "json",
"granularity": "full"
}
}
}PDFs
/document/ocr handles them directly. Everything else that is a document should pass through /document/convert first.
Photographs and screenshots
/image/ocr accepts them without conversion, which matters for receipt capture and mobile uploads.
Mixed batches
Filter by type early so a single unexpected format does not fail an otherwise valid Assembly.
Choose the output shape before you choose the provider
The format parameter has more effect on the surrounding pipeline than any other choice. json and text both return a file, which suits archiving and indexing. meta returns no file at all and instead stores the strings on the file object under ${file.meta.recognized_text}, where later Steps can read them. That is what makes it possible to filter on recognised content, or to burn extracted text into an image, without running recognition twice.
granularity follows from the same question. full returns one block, which is what a search index wants. list returns positioned fragments, which is what you need to redact a region, pull a specific field, or highlight a match in a viewer. Choosing list when you only need a search index costs nothing but produces output that is more work to consume.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"recognized": {
"use": ":original",
"robot": "/document/ocr",
"format": "meta",
"granularity": "full",
"provider": "gcp"
},
"only_invoices": {
"use": "recognized",
"robot": "/file/filter",
"accepts": [
["${file.meta.recognized_text}", "regex", "(?i)invoice"]
]
}
}
}meta
Keeps text on the file object for downstream Steps. No file is produced.
json and text
Produce a file to store or index. Use these when the text leaves the Assembly.
list granularity
Adds positions, which redaction and field extraction need and a search index does not.
Prepare the input rather than switching providers
When recognition quality disappoints, the reflex is to try the other provider. Input preparation usually pays better. Scans arrive rotated, skewed, and unevenly lit, and every one of those costs accuracy before the model sees the page. Running /document/autorotate ahead of recognition corrects orientation, and normalising contrast on photographed pages removes a common source of dropped lines.
Cost behaves the same way. OCR carries a minimum charge of one megabyte per file, so a hundred single-page scans submitted individually cost considerably more than the same hundred pages batched through one Assembly. Batch where the workload allows it, and keep interactive recognition separate from bulk backfills so a large import cannot delay a user waiting on a single upload.
Deskew first
/document/autorotate before recognition fixes the most common cause of poor results.
Batch small files
The one-megabyte minimum charge per file makes per-page Assemblies expensive at volume.
Separate queues
Keep bulk backfills away from interactive uploads so users are not queued behind an import.
Treat recognised text as evidence, not as a record
Both Robots call third-party AI services, and those providers retrain their models. The same PDF can return slightly different text months later. That is manageable as long as the system does not assume otherwise: store the recognised text next to the source file along with the provider and the date it was produced, so a later difference is visible rather than silent.
The same property makes exact-match assertions a poor choice in tests. Assert that a known phrase appears, or that a field parses, rather than comparing a full transcript. Where a value has legal or financial consequence, keep the source document as the record and treat the extracted text as an index into it.
Store provenance
Keep the provider and run date with the text so drift can be detected later.
Assert loosely
Test for the presence of expected content rather than an exact transcript.
Keep the original
The source document remains the record; recognised text is a way of finding it.
Pull individual fields instead of whole transcripts
Plenty of workloads do not want the text of a document at all. They want an invoice number, a date, a total, or the region of a page that must be blacked out before the file is shared. That is where granularity: "list" earns its cost: it returns fragments with positions, so a downstream Step can select by location rather than by parsing a single undifferentiated block.
Anchoring on layout alone is fragile, because a supplier redesigns a form and every coordinate moves. Anchoring on a nearby label and using position only to disambiguate between repeated matches survives that redesign. Where the document set is genuinely unpredictable, passing the recognised text to a model with a strict output schema is more robust than an expanding collection of regular expressions.
Positions for redaction
Fragment coordinates let a later Step cover a region rather than rewriting the file.
Anchor on labels
Find the text next to a known label, and use position only to break ties.
Schemas over patterns
For unpredictable layouts, a typed extraction schema ages better than accumulated regular expressions.
Decide what happens when a page cannot be read
Every archive contains pages that recognition cannot handle: a photograph of a screen, a fax of a fax, handwriting in a margin. Returning empty text for those is correct behaviour, and the pipeline needs somewhere for them to go. Treating an empty or very short result as a routing signal rather than as a failure keeps the batch moving and puts the exceptions in front of a person.
Retrying the same file against the same provider rarely helps, because nothing about the input changed. Retrying after a preparation Step such as autorotation sometimes does. Recording which files produced nothing, and how many, turns an invisible quality problem into a number that can be watched over time.
Empty is a signal
Route short or empty results to review instead of counting them as failures.
Retry differently
A second attempt only helps if the input was prepared differently the second time.
Track the rate
Measuring unreadable pages over time reveals scanner and intake problems at their source.
Technical details worth knowing
- /document/ocr and /image/ocr both accept the provider, granularity, and format parameters. The provider defaults to automatic selection, and can be pinned to aws or gcp.
- The format parameter accepts json, meta, and text. The meta option returns no file and instead stores the strings on the file object under ${file.meta.recognized_text}, which later Steps can read.
- The granularity parameter accepts full and list. Use list when you need per-fragment positions for redaction or field extraction, and full when you only need the text.
- OCR carries a minimum charge of one megabyte per file, so batching many small scans into one Assembly is cheaper than issuing one Assembly per page.
- Recognition quality depends far more on input preparation than on provider choice. Deskewing with /document/autorotate and normalising contrast before recognition usually beats switching providers.
- Because output varies as providers retrain, store the recognised text alongside the source file and the date it was produced, so a later change is visible rather than silent.
A practical approach
- 1
Split the input by type: PDFs to /document/ocr, photographs to /image/ocr, everything else through /document/convert first.
- 2
Deskew scans with /document/autorotate before recognition.
- 3
Choose the meta format when a later Step consumes the text, and json or text when you are storing it.
- 4
Store the text with the source file, the provider, and the run date so drift stays visible.
When Transloadit is useful
Use /document/ocr for PDFs and /image/ocr for photographs and screenshots. Convert other document formats to PDF with /document/convert first. Choose the meta format when later Steps need the text, and the json or text formats when you are storing it.
Architecture boundary
OCR runs on third-party AI services whose models change over time, so the same input can return different text later. Treat recognised text as searchable evidence rather than an authoritative record, and never assert exact strings in tests.
Frequently asked questions
Why does /document/ocr return nothing for my Word file?
It currently accepts PDFs only. Run /document/convert first to produce a PDF, then pass that result to /document/ocr. The same applies to PowerPoint files and to image-based documents such as multi-page TIFFs.
Should I use /document/ocr or /image/ocr?
Use /document/ocr for PDFs and /image/ocr for photographs, screenshots, and camera captures. If a single upload endpoint receives both, split the traffic with a /file/filter Step rather than sending everything to one Robot.
How do I use the extracted text in a later Step?
Set format: "meta". No file is returned, and the strings are stored on the file object under ${file.meta.recognized_text}, which later Steps can read for filtering, watermarking, or routing. Use json or text instead when the text is leaving the Assembly to be stored or indexed.
Can I get consistent results across runs?
Pinning provider to aws or gcp removes variation from automatic selection, but providers still retrain their own models, so output can change over time regardless. Store the text with its provider and run date, and avoid asserting exact strings in tests.
What is the cheapest way to process a large scan archive?
Batch files into fewer Assemblies. Each file carries a minimum charge of one megabyte, so per-page Assemblies are markedly more expensive at volume. Run the backfill separately from interactive traffic so a long import does not delay uploads that a user is waiting on.