How to Analyze Documents with AWS Textract and Comprehend

- Use Textract to read documents and Comprehend to interpret the resulting text.
- Pick the Textract API that matches the document: text detection for OCR, document analysis for forms and tables, and expense analysis for invoices and receipts.
- Use synchronous APIs for small, single-page requests. Use asynchronous jobs for multipage PDFs, TIFF files, and batch workflows.
- Put SQS between event-driven stages so traffic spikes do not overwhelm Lambda functions or downstream APIs.
- Chunk text according to the limit of every Comprehend API being called. Sentiment detection, for example, accepts less text than entity or key-phrase detection.
- Treat confidence scores as signals, not guarantees. Send low-confidence or high-risk results to a reviewer.
- Calculate costs using the exact Textract features and Comprehend operations required. Each additional analysis feature or NLP API can change the total substantially.
Invoices, contracts, claims, and medical records often contain valuable information, but most of it is trapped inside PDFs and scanned images. Manually copying that information into another system may work for a few documents. At production scale, it quickly becomes slow, expensive, and difficult to audit.
AWS gives us two managed services that solve different parts of this problem:
- Amazon Textract extracts text and document structure.
- Amazon Comprehend finds meaning in the extracted text.
Together, they can turn unstructured files into data we can validate, search, route, and store. The services remove much of the machine-learning infrastructure, but a reliable production system still needs careful API selection, asynchronous processing, security controls, and human review.
Where Textract Ends and Comprehend Begins
Textract and Comprehend are complementary, but they are not interchangeable.
| Service | Reads | Produces | Typical questions it answers |
| Amazon Textract | PDF, TIFF, JPEG, and PNG documents | Words, lines, key-value pairs, tables, queries, signatures, and layout blocks | “What is written here?” and “Where is it on the page?” |
| Amazon Comprehend | UTF-8 text | Entities, key phrases, sentiment, PII findings, and custom classifications | “What does this text mean?” and “How should we categorize it?” |
| Amazon Comprehend Medical | US English clinical text | Medical entities, protected health information, and ontology-linked concepts through separate APIs | “Which conditions, medications, procedures, or PHI appear here?” |
What Amazon Textract extracts
Textract goes beyond conventional OCR. Depending on the API and requested features, it can return:
| Feature | What it extracts |
| Text | Printed or handwritten words and lines |
| Forms | Key-value relationships such as Invoice number: INV-1048 |
| Tables | Cells, rows, columns, titles, headers, and merged-cell relationships |
| Queries | Answers to questions such as “What is the payment due date?” |
| Signatures | Detected signature locations and confidence scores |
| Layout | Titles, section headers, paragraphs, lists, tables, figures, headers, and footers |
Textract returns these findings as interconnected Block objects. A parser follows block relationships to reconstruct a form, table, or page rather than treating the response as one long string.
What Amazon Comprehend adds
Once we have readable text, Comprehend can add semantic information:
- Entity recognition: people, organizations, dates, quantities, locations, and other named entities
- Key-phrase extraction: important topics and noun phrases
- Sentiment analysis: positive, negative, neutral, or mixed sentiment
- PII detection: sensitive items and their character offsets
- Custom classification: labels based on examples from our domain
- Custom entity recognition: domain-specific entities that the built-in model does not cover
Comprehend does not need Textract in every workflow. If the source already contains clean text, we can send that text directly to Comprehend.
Choose the Right Textract API First
Using the most specialized API usually gives us a cleaner response and prevents us from paying for features we do not need.
| Requirement | Synchronous API | Asynchronous API | Best fit |
| Raw words and lines | DetectDocumentText | StartDocumentTextDetection | Searchable text and basic OCR |
| Forms, tables, queries, signatures, or layout | AnalyzeDocument | StartDocumentAnalysis | Contracts, applications, and general forms |
| Invoices and receipts | AnalyzeExpense | StartExpenseAnalysis | Normalized fields such as vendor, total, tax, and line items |
| Identity documents | AnalyzeID | — | US passports and driver’s licences |
| Mortgage documents | — | StartLendingAnalysis | Lending packages and document classification |
For an invoice pipeline, generic form parsing should not be our default. AnalyzeExpense normalizes common financial fields even when different vendors use different labels and layouts.
Synchronous or asynchronous?
Synchronous calls are useful when a user is waiting for a result from a small, single-page document. They accept a limited document size and return the response in the same request.
Asynchronous jobs are the better choice for multipage PDFs and TIFF files. The document must be in Amazon S3, and Textract sends a completion notification when processing finishes. At the time of writing, asynchronous PDF and TIFF inputs can contain up to 3,000 pages and be up to 500 MB, subject to current service quotas.
A Production-Ready Document Analysis Architecture
A resilient serverless pipeline can look like this:
S3 input → SQS intake → Lambda starter → Textract
↓
S3 results ← Lambda processor ← SQS results ← SNS completion
↓
Comprehend or Comprehend Medical → DynamoDB, OpenSearch, or an application databaseThe two queues are deliberate. The intake queue absorbs upload bursts before we start jobs. The result queue buffers completion events before we retrieve, transform, and enrich the output. Both queues can have dead-letter queues for messages that repeatedly fail.
How the workflow runs
- A client uploads a document to an encrypted S3 input bucket.
- The S3 event is delivered to an SQS intake queue.
- A Lambda function validates the object and starts the correct asynchronous Textract job.
- Textract publishes job completion to an SNS topic in the same AWS Region.
- SNS delivers the event to an SQS result queue.
- A result-processing Lambda retrieves every response page using
NextToken. - The function stores the original Textract response and a normalized representation in S3.
- Text is divided into safe chunks and sent to Comprehend or Comprehend Medical.
- Structured results are stored in the database or search index used by the application.
For workflows with branching, timeouts, approvals, or several enrichment steps, AWS Step Functions can make orchestration and failure handling easier to understand than a long chain of Lambda functions.
Reliability details that matter
- Pass a stable
ClientRequestTokenwhen starting an asynchronous job. Retrying the same request then returns the existing job rather than creating duplicate work. - Add a
JobTagor maintain a job table so completion events can be correlated with the source document and tenant. - Configure
OutputConfigwhen we need Textract output in our own S3 bucket. Otherwise, asynchronous results are kept in Textract-managed storage for a limited period. - Retrieve all pages of results. A successful first
GetDocumentAnalysisresponse may still contain aNextToken. - Use exponential backoff and jitter for throttling errors, and tune Lambda concurrency to the quotas of downstream services.
- Make result processing idempotent because SQS and SNS can deliver a message more than once.
Serverless does not mean unlimited. Textract job quotas, Lambda concurrency, SQS visibility timeouts, and downstream write capacity still need monitoring.
Partner with Us for Success
Experience seamless collaboration and exceptional results.
Working Python Examples
The following examples use boto3. Production code should also include structured logging, metrics, retries, permission boundaries, and application-specific validation.
Analyze a single-page document synchronously
Use AnalyzeDocument when a small, single-page document needs forms, tables, or signatures:
import boto3
textract = boto3.client("textract", region_name="ap-south-1")
def analyze_document(bucket: str, key: str) -> list[dict]:
response = textract.analyze_document(
Document={"S3Object": {"Bucket": bucket, "Name": key}},
FeatureTypes=["FORMS", "TABLES", "SIGNATURES"],
)
return response["Blocks"]If we only need text, detect_document_text is simpler and less expensive. If the document is an invoice or receipt, analyze_expense is usually a better fit.
Start an asynchronous analysis job
For a multipage form, we can start a job and let Textract notify us through SNS:
import hashlib
import boto3
textract = boto3.client("textract", region_name="ap-south-1")
SNS_TOPIC_ARN = "arn:aws:sns:ap-south-1:123456789012:textract-complete"
TEXTRACT_ROLE_ARN = "arn:aws:iam::123456789012:role/TextractPublishRole"
OUTPUT_BUCKET = "document-analysis-results"
def start_document_analysis(bucket: str, key: str, version_id: str = "") -> str:
source = f"{bucket}:{key}:{version_id}"
request_token = hashlib.sha256(source.encode("utf-8")).hexdigest()
response = textract.start_document_analysis(
DocumentLocation={
"S3Object": {
"Bucket": bucket,
"Name": key,
**({"Version": version_id} if version_id else {}),
}
},
FeatureTypes=["FORMS", "TABLES", "LAYOUT"],
ClientRequestToken=request_token,
JobTag=request_token[:32],
NotificationChannel={
"SNSTopicArn": SNS_TOPIC_ARN,
"RoleArn": TEXTRACT_ROLE_ARN,
},
OutputConfig={
"S3Bucket": OUTPUT_BUCKET,
"S3Prefix": f"textract/{request_token}",
},
)
return response["JobId"]If the input bucket uses versioning, including the S3 version ID in the idempotency key prevents two versions of the same object name from being treated as one job.
Retrieve every result page
The result processor should call this only after the completion message reports SUCCEEDED:
def get_document_analysis(job_id: str) -> list[dict]:
blocks = []
next_token = None
while True:
request = {"JobId": job_id}
if next_token:
request["NextToken"] = next_token
response = textract.get_document_analysis(**request)
if response["JobStatus"] != "SUCCEEDED":
raise RuntimeError(
f"Textract job {job_id} is {response['JobStatus']}"
)
blocks.extend(response.get("Blocks", []))
next_token = response.get("NextToken")
if not next_token:
return blocksParse key-value pairs from forms
Textract represents keys and values as related blocks. This parser also preserves selected checkboxes and radio buttons:
def block_text(block: dict, block_map: dict[str, dict]) -> str:
parts = []
for relationship in block.get("Relationships", []):
if relationship["Type"] != "CHILD":
continue
for child_id in relationship["Ids"]:
child = block_map.get(child_id, {})
if child.get("BlockType") == "WORD":
parts.append(child.get("Text", ""))
elif child.get("BlockType") == "SELECTION_ELEMENT":
if child.get("SelectionStatus") == "SELECTED":
parts.append("SELECTED")
return " ".join(parts).strip()
def extract_form_fields(blocks: list[dict]) -> dict[str, str]:
block_map = {block["Id"]: block for block in blocks}
values = {
block["Id"]: block
for block in blocks
if block.get("BlockType") == "KEY_VALUE_SET"
and "VALUE" in block.get("EntityTypes", [])
}
fields = {}
for key in blocks:
if key.get("BlockType") != "KEY_VALUE_SET":
continue
if "KEY" not in key.get("EntityTypes", []):
continue
key_text = block_text(key, block_map)
for relationship in key.get("Relationships", []):
if relationship["Type"] != "VALUE":
continue
for value_id in relationship["Ids"]:
if value_id in values:
fields[key_text] = block_text(values[value_id], block_map)
return fieldsReal forms can contain repeated labels such as Date or Total. A production schema should not rely on a plain dictionary when duplicate keys are meaningful; it should retain page number, geometry, confidence, and source block IDs.
Send safe text chunks to Comprehend
Comprehend limits are operation-specific. DetectSentiment accepts up to 5 KB of UTF-8 text, while the limits for DetectEntities and DetectKeyPhrases are larger. Because this example calls all three operations on the same chunk, it uses a 4,500-byte ceiling.
import boto3
comprehend = boto3.client("comprehend", region_name="ap-south-1")
def chunk_utf8(text: str, max_bytes: int = 4_500) -> list[str]:
chunks = []
current = []
for word in text.split():
if len(word.encode("utf-8")) > max_bytes:
raise ValueError("A single token exceeds the configured byte limit")
candidate = " ".join([*current, word])
if current and len(candidate.encode("utf-8")) > max_bytes:
chunks.append(" ".join(current))
current = [word]
else:
current.append(word)
if current:
chunks.append(" ".join(current))
return chunks
def analyze_text(text: str) -> list[dict]:
results = []
for chunk in chunk_utf8(text):
entities = comprehend.detect_entities(Text=chunk, LanguageCode="en")
sentiment = comprehend.detect_sentiment(Text=chunk, LanguageCode="en")
phrases = comprehend.detect_key_phrases(Text=chunk, LanguageCode="en")
results.append(
{
"text": chunk,
"entities": entities["Entities"],
"sentiment": sentiment["Sentiment"],
"sentiment_scores": sentiment["SentimentScore"],
"key_phrases": phrases["KeyPhrases"],
}
)
return resultsWe keep sentiment at the chunk level because collapsing a long contract, review, or conversation into one label can hide important sections. For better context, a production chunker should preserve sentence or paragraph boundaries and record page references.
For large collections, asynchronous Comprehend jobs are usually more efficient than making thousands of synchronous calls. They also support larger input documents, subject to the limits of the selected operation.
Analyze clinical text with Comprehend Medical
Comprehend Medical uses separate operations for medical entities, PHI, and ontology linking:
import boto3
medical = boto3.client("comprehendmedical", region_name="us-west-2")
def analyze_medical_text(text: str) -> dict:
entities = medical.detect_entities_v2(Text=text)
phi = medical.detect_phi(Text=text)
return {
"medical_entities": entities["Entities"],
"phi": phi["Entities"],
}DetectEntitiesV2 and DetectPHI accept up to 20 KB per request. Mapping diagnoses to ICD-10-CM, medications to RxNorm, or clinical concepts to SNOMED CT requires InferICD10CM, InferRxNorm, or InferSNOMEDCT respectively, each with its own input limit and price.
The example uses us-west-2 because Comprehend Medical is not available in every Region, including Asia Pacific (Mumbai) at the time of writing. We should confirm current Region availability and data-residency requirements before designing the workflow.
Comprehend Medical is designed to assist healthcare workflows, not replace clinical judgment. AWS also warns that it may not identify every item of protected health information, so its output should not be the only control used for compliance-critical de-identification.
What Does Document Analysis Cost?
There is no reliable flat price per document. Textract charges per page and requested feature, while Comprehend charges per text unit and operation. Document length, request size, Region, free-tier eligibility, and custom models all affect the total.
The following examples use public first-tier pricing for US West (Oregon) and 1,000 one-page documents, excluding free-tier benefits. They are illustrations, not quotes.
| Analysis choice | Approximate processing charge |
| Textract text detection only | $1.50 |
| Textract expense analysis for invoices or receipts | $10.00 |
| Textract forms only | $50.00 |
| Textract forms and tables | $65.00 |
| Textract forms, tables, and queries | $70.00 |
| Comprehend on 1 million characters, one built-in API | About $1.00 |
| Comprehend on 1 million characters, three built-in APIs | About $3.00 |
| Comprehend Medical entity recognition on 1 million characters | About $100.00 |
The Comprehend figures assume 10,000 units of 100 characters. Built-in APIs have a minimum charge of three units per request, so many small requests can cost more than a few well-sized requests. Each API is billed separately.
Comprehend Medical also bills each operation separately. PHI detection and ontology-linking calls therefore add to entity-recognition costs. S3, SQS, SNS, Lambda, KMS, logging, database, and data-transfer charges should be calculated separately for the actual workload and Region.
The most useful cost optimization is simple: request only what the product needs. Paying for forms, tables, queries, and three NLP operations on every document “just in case” is rarely a good default.
Security, Privacy, and Human Review
Document pipelines often process financial, legal, personal, or clinical data. Security cannot be bolted on after extraction works.
Protect the data
- Encrypt input, output, and database records at rest with appropriate AWS KMS keys.
- Use TLS for data in transit and block public access to document buckets.
- Apply least-privilege IAM policies to each Lambda function and Textract notification role.
- Separate tenants with explicit authorization checks; an S3 object key is not an access-control decision.
- Avoid writing document contents, PII, PHI, or full Textract responses to application logs.
- Define lifecycle and deletion policies for source documents, intermediate results, and backups.
- Record access and administrative activity with services such as AWS CloudTrail, then alert on suspicious events.
- Consider VPC endpoints when private connectivity is part of the security design.
Treat HIPAA eligibility correctly
Amazon Textract and Amazon Comprehend Medical can be used in HIPAA-eligible workloads, but using an eligible service does not make an application compliant by itself. We still need an applicable AWS Business Associate Addendum, correct configuration, access controls, encryption, auditability, retention rules, and organizational safeguards.
Build a review path
Every extracted item includes context we can use for a decision: confidence, field type, document type, and business risk. A misspelled marketing preference and a misread invoice total should not share the same review threshold.
A practical workflow automatically accepts high-confidence, low-risk fields; validates values against business rules; and sends uncertain or high-impact fields to a person. Reviewer corrections can then become evaluation data for parsers, Queries, custom classifiers, or Textract adapters.
Partner with Us for Success
Experience seamless collaboration and exceptional results.
Best Practices That Matter in Production
Start with representative documents
Test native PDFs, scans, handwriting, rotated pages, faint text, unusual tables, and the worst files customers actually upload. Average-quality samples can hide the failure modes that determine whether the product is trustworthy.
AWS documents a minimum detectable text height rather than one universal DPI requirement. We should prefer native PDFs where available and ensure scanned text is large and clear enough, then measure accuracy on our own documents.
Preserve the original response
Normalized JSON is easier for an application to consume, but it can discard geometry and relationships that become useful later. Keeping the original Textract output in an encrypted, lifecycle-managed bucket lets us reprocess without running extraction again.
Preserve document context
When building text for Comprehend, keep page numbers, headings, paragraph boundaries, and source block IDs. This makes findings explainable and lets a user return to the exact place where an entity or phrase appeared.
Validate outputs against the domain
Confidence alone is not validation. Dates should parse, totals should reconcile, identifiers should match expected formats, and values should be checked against known records where appropriate.
Measure quality continuously
Maintain a labelled evaluation set and track field-level precision, recall, straight-through-processing rate, reviewer correction rate, latency, and cost per document. Rerun the evaluation when layouts, adapters, parsers, or service configurations change.
Design for throttling and failure
Use bounded concurrency, retries with jitter, dead-letter queues, idempotent writes, alarms, and replay tooling. A failed document should be traceable and safely replayable without duplicating records or charges unnecessarily.
Common Mistakes to Avoid
| Mistake | Better approach |
| Using generic form analysis for every invoice | Use AnalyzeExpense or StartExpenseAnalysis and evaluate its normalized fields |
| Enabling every Textract feature | Select only the features required by the product |
| Sending S3 events directly into unbounded processing | Add SQS for buffering, retries, backpressure, and dead-letter handling |
| Polling every Textract job continuously | Use SNS completion notifications, buffered through SQS |
| Assuming a serverless pipeline has no capacity limits | Monitor quotas and control concurrency at each stage |
| Combining all pages into context-free text | Preserve page, section, and block references |
| Applying a 5 KB limit to every Comprehend API | Enforce the documented byte limit of each selected operation |
| Treating confidence as proof of correctness | Add domain validation and risk-based human review |
| Logging complete requests and responses | Log identifiers and metrics while keeping sensitive content out of logs |
| Calling Medical entity detection and expecting ontology codes | Use the separate ICD-10-CM, RxNorm, or SNOMED CT inference API |
Frequently Asked Questions
What is the difference between Amazon Textract and traditional OCR?
Traditional OCR mainly returns characters, words, and lines. Textract can also identify relationships such as form keys and values, table cells, layout elements, query answers, and signatures, together with geometry and confidence.
When should we use synchronous or asynchronous Textract?
Use synchronous APIs for small, single-page requests that need an immediate response. Use asynchronous jobs for multipage PDFs, TIFF files, batch processing, or workflows that need buffering, notifications, retries, and independent scaling.
Which Textract API should we use for invoices and receipts?
Use AnalyzeExpense for synchronous processing or StartExpenseAnalysis for asynchronous processing. These APIs normalize common invoice and receipt fields, reducing custom label matching required with generic forms and tables in downstream systems.
Is a Textract and Comprehend Medical pipeline automatically HIPAA compliant?
No. The services are HIPAA eligible, but compliance depends on the complete workload, an applicable AWS agreement, encryption, least-privilege access, audit controls, retention practices, operational safeguards, and appropriate human oversight.
How can we improve accuracy for recurring custom document layouts?
Start with Queries, strong validation, and a labelled evaluation set. For supported use cases, Textract adapters can customize query responses using annotated examples, but they should still be tested against representative documents and edge cases.
Final Thoughts
A useful document-intelligence system does more than extract text. It chooses the right parser, preserves structure, adds semantic context, validates business-critical values, protects sensitive data, and makes uncertain results reviewable.
Textract and Comprehend give us strong managed building blocks for that system. We can begin with a small set of representative documents, measure extraction quality and cost, and then introduce queues, asynchronous jobs, durable results, NLP enrichment, and human review as the workflow moves toward production.
That approach keeps the architecture practical: automation handles the repetitive work, while validation and review protect the decisions that matter.



