Blogs/Technology

How to Analyze Documents with AWS Textract and Comprehend

Written byShubham Ambastha
Aug 17, 2026
12 Min Read
How to Analyze Documents with AWS Textract and Comprehend Hero
Too Long? Read This First

- 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.

ServiceReadsProducesTypical questions it answers
Amazon TextractPDF, TIFF, JPEG, and PNG documentsWords, lines, key-value pairs, tables, queries, signatures, and layout blocks“What is written here?” and “Where is it on the page?”
Amazon ComprehendUTF-8 textEntities, key phrases, sentiment, PII findings, and custom classifications“What does this text mean?” and “How should we categorize it?”
Amazon Comprehend MedicalUS English clinical textMedical entities, protected health information, and ontology-linked concepts through separate APIs“Which conditions, medications, procedures, or PHI appear here?”
Amazon Textract
Reads
PDF, TIFF, JPEG, and PNG documents
Produces
Words, lines, key-value pairs, tables, queries, signatures, and layout blocks
Typical questions it answers
“What is written here?” and “Where is it on the page?”
1 of 3

What Amazon Textract extracts

Textract goes beyond conventional OCR. Depending on the API and requested features, it can return:

FeatureWhat it extracts
TextPrinted or handwritten words and lines
FormsKey-value relationships such as Invoice number: INV-1048
TablesCells, rows, columns, titles, headers, and merged-cell relationships
QueriesAnswers to questions such as “What is the payment due date?”
SignaturesDetected signature locations and confidence scores
LayoutTitles, section headers, paragraphs, lists, tables, figures, headers, and footers
Text
What it extracts
Printed or handwritten words and lines
1 of 6

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.

RequirementSynchronous APIAsynchronous APIBest fit
Raw words and linesDetectDocumentTextStartDocumentTextDetectionSearchable text and basic OCR
Forms, tables, queries, signatures, or layoutAnalyzeDocumentStartDocumentAnalysisContracts, applications, and general forms
Invoices and receiptsAnalyzeExpenseStartExpenseAnalysisNormalized fields such as vendor, total, tax, and line items
Identity documentsAnalyzeIDUS passports and driver’s licences
Mortgage documentsStartLendingAnalysisLending packages and document classification
Raw words and lines
Synchronous API
DetectDocumentText
Asynchronous API
StartDocumentTextDetection
Best fit
Searchable text and basic OCR
1 of 5

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 database

The 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

  1. A client uploads a document to an encrypted S3 input bucket.
  2. The S3 event is delivered to an SQS intake queue.
  3. A Lambda function validates the object and starts the correct asynchronous Textract job.
  4. Textract publishes job completion to an SNS topic in the same AWS Region.
  5. SNS delivers the event to an SQS result queue.
  6. A result-processing Lambda retrieves every response page using NextToken.
  7. The function stores the original Textract response and a normalized representation in S3.
  8. Text is divided into safe chunks and sent to Comprehend or Comprehend Medical.
  9. 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 ClientRequestToken when starting an asynchronous job. Retrying the same request then returns the existing job rather than creating duplicate work.
  • Add a JobTag or maintain a job table so completion events can be correlated with the source document and tenant.
  • Configure OutputConfig when 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 GetDocumentAnalysis response may still contain a NextToken.
  • 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 blocks

Parse 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 fields

Real 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 results

We 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 choiceApproximate 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 APIAbout $1.00
Comprehend on 1 million characters, three built-in APIsAbout $3.00
Comprehend Medical entity recognition on 1 million charactersAbout $100.00
Textract text detection only
Approximate processing charge
$1.50
1 of 8

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

MistakeBetter approach
Using generic form analysis for every invoiceUse AnalyzeExpense or StartExpenseAnalysis and evaluate its normalized fields
Enabling every Textract featureSelect only the features required by the product
Sending S3 events directly into unbounded processingAdd SQS for buffering, retries, backpressure, and dead-letter handling
Polling every Textract job continuouslyUse SNS completion notifications, buffered through SQS
Assuming a serverless pipeline has no capacity limitsMonitor quotas and control concurrency at each stage
Combining all pages into context-free textPreserve page, section, and block references
Applying a 5 KB limit to every Comprehend APIEnforce the documented byte limit of each selected operation
Treating confidence as proof of correctnessAdd domain validation and risk-based human review
Logging complete requests and responsesLog identifiers and metrics while keeping sensitive content out of logs
Calling Medical entity detection and expecting ontology codesUse the separate ICD-10-CM, RxNorm, or SNOMED CT inference API
Using generic form analysis for every invoice
Better approach
Use AnalyzeExpense or StartExpenseAnalysis and evaluate its normalized fields
1 of 10

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.

Author-Shubham Ambastha
Shubham Ambastha

I'm a Senior Software Developer with 4.5+ years of experience in building optimized, cost-effective web applications. Passionate about coding and innovation, I create impactful tech solutions.

Share this article

Phone

Next for you

8 Best GraphQL Libraries for Node.js in 2025 Cover

Technology

Aug 4, 202613 min read

8 Best GraphQL Libraries for Node.js in 2025

8 Best GraphQL Libraries for Node.js in 2026 Too Long? Read This First - Choose Apollo Server when you need a mature ecosystem, GraphOS integration, plugins, or Apollo Federation. - Choose GraphQL Yoga for a modern, portable server with Fetch API compatibility and built-in support for subscriptions over Server-Sent Events. - Choose Mercurius when your application already uses Fastify and runtime efficiency is a major priority. - Use GraphQL.js when you need the official JavaScript implementati

9 React Native Animation Libraries and Tools Compared Cover

Technology

Aug 4, 202615 min read

9 React Native Animation Libraries and Tools Compared

Too Long? Read This First - Use React Native Reanimated for gesture-driven, interruptible, and performance-sensitive interface animations. - Use the built-in Animated API for simple fades, transforms, and timed sequences without another dependency. - Pair React Native Gesture Handler with Reanimated for swipes, dragging, pinching, rotation, and other touch-driven experiences. - Use Lottie React Native for non-interactive motion graphics supplied by designers. - Choose React Native Skia for cust

9 Critical Practices for Secure Web Application Development Cover

Technology

Aug 4, 202616 min read

9 Critical Practices for Secure Web Application Development

Too Long? Read This First - Define security requirements and model threats before implementation begins. - Treat authentication, account recovery, and MFA as one complete identity system. - Apply server-side authorization to every protected action and object. - Prevent injection with parameterized APIs, structured validation, safe output handling, and restricted outbound requests. - Protect sessions and tokens throughout their complete lifecycle. - Minimise sensitive data and manage encryption