Self-Deployed Container
v3.7.0 (latest)

Getting Started with Document Extraction

This guide shows how to deploy and use the document extraction container for processing PDF documents. The container runs an HTTP server that accepts PDF bytes and returns structured extraction results. The output uses the same JSON structure as the hosted Extract API, documented on the Output Format page. The JSON is designed to be consumed directly. Optionally, the open-source Kenverters toolkit can convert it to markdown, plain text, or pandas DataFrames if one of those formats is more convenient.

The container supports native (text-based) PDFs, scanned PDFs, and mixed documents (pages with both native text and scanned images). OCR runs by default on pages that lack extractable text, and can be turned off.

What Changed in v3.7.0

  • OCR is more accurate on scanned documents. Handling is chosen automatically for each page, so a document that mixes languages needs no configuration, and a language outside the supported list still processes as it did in v3.6.0 rather than failing. See OCR for Scanned Documents.
  • Four new request options bring the container closer to the hosted Extract v3 API. ocr forces OCR on every page, language applies one language to the whole document instead of detecting each page, pages extracts only a selection of pages, and return_absolute_pdf_page_numbers numbers output pages as the source PDF numbers them. All four default to v3.6.0 behavior. See POST /extract.
  • Peak memory is higher while processing scanned documents. An allocation sized for v3.6.0 may no longer be enough, so check it against your own documents before upgrading. See Resource Tuning.
  • A new optional setting, MAX_OCR_PAGE_MEGAPIXELS, rejects documents containing a page above a size limit you set. It defaults to 0, which enforces no limit, so nothing changes unless you set it. Worth setting if your documents can contain very large scanned pages. Without it, such a page can exhaust the container's memory. See Very large scanned pages.
  • Figure extraction tables are named and split differently. Every chart type now uses the same column names, so names such as Entity and Amount that varied by chart type are gone. A bar chart with a line drawn over it and two value axes now produces two tables instead of one. Figure tables will differ from v3.6.0 on any document with charts, including native PDFs. If your code reads figure tables by column name or expects one table per figure, update it before upgrading. See figure_extraction.

The JSON structure of the response is unchanged from v3.6.0, though the figure tables inside it change as described above. There are four new request options, one new environment variable, and three new 422 error codes, all listed below.

Prerequisites

  • Docker or any OCI-compatible container runtime
  • No GPU required (CPU-only ML inference)

Many of the examples in this guide use Kubernetes and deploy the extraction container as a sidecar to your main application. Neither is required. The container is a standalone HTTP server that runs anywhere you can run an OCI image, and your application can call it over any network path that can reach it.

Container Image

208007848330.dkr.ecr.us-east-1.amazonaws.com/kensho-containerized-extract:X.Y.Z

Access to the image is provisioned by Kensho. If you do not have access yet, email extract@kensho.com.

Endpoints

GET /health

Returns {"status": "ready"} once the server is up. Use it as a readiness and liveness check. While the ML models are still loading, a request to /health blocks rather than returning an error, and completes as soon as loading finishes. The endpoint also responds while an extraction is in progress.

POST /extract

Accepts a PDF document and returns structured extraction results.

Request format: multipart/form-data with two fields:

FieldTypeRequiredDescription
documentfile uploadYesThe PDF file to extract. Supports native (text-based) PDFs, scanned PDFs, and mixed documents. OCR runs by default on pages that lack extractable text (see OCR for Scanned Documents). Maximum size: 500 MB (configurable).
optionsform field (string)NoA JSON string containing extraction parameters. Defaults to {} (all defaults applied).

Options JSON fields (all optional):

output_format

string Default: structured_document_with_locations

Controls the structure and detail level of the extraction output. See Output Format for the full JSON structure.

  • structured_document - Core document structure (text blocks, tables, figures) without positional information. Smallest output.
  • structured_document_with_locations - (Default) Includes bounding box coordinates for each element. Use when you need to know where content appears on the page.
  • structured_document_with_char_offsets - Includes character-level offset information in addition to locations. Use when you need to map extracted text back to exact positions in the source PDF.

figure_extraction

bool Default: true

When enabled, figures in the document are detected, classified by type (bar chart, pie chart, scatter plot, line plot), and parsed into structured data. Disabling this skips parsing figures into tables, which reduces processing time for documents where figure data is not needed. Figures are still detected and labeled by chart type.

Each parsed figure is returned as a table; a bar chart with a line drawn over it and two value axes is returned as two tables, first the bars and then the line. A chart's axis columns are named after its axis titles when it has them. Otherwise they are Category and Value for bar charts, or X Value and Y Value for line and scatter plots. The legend wedge sizes, or (Labeled) for values printed on the chart. Pie charts have a Category column for the wedge names, plus Value and Percent columns.

enhanced_table_extraction

bool Default: true

When enabled, identifies rows, columns, and cell boundaries for more accurate table extraction, especially on complex tables. Disabling it is faster but less accurate.

include_images

bool Default: false

When enabled, includes the locations of the images.

include_relations

bool Default: false

When enabled, includes relationships between document layout segments.

extract_table_row_header_hierarchy

bool Default: false

When enabled, includes the hierarchical relationships among table row headers.

merge_multi_page_tables

bool Default: false

Detects tables that continue across consecutive pages and merges them into a single table annotation. Recommended for multi-page documents where tables commonly span page breaks: enabling it yields one coherent table instead of several fragments.

ocr

bool Default: false

By default only pages with no extractable text are OCR'd. Set this to true to OCR every page, including pages whose text could be read natively. Every page then goes through OCR, so extraction takes considerably longer. This requires OCR to be enabled. On an instance running ENABLE_DOCUMENT_OCR=false, a request that sets this returns 422 ocr_not_available.

language

string or null Default: null (detect each page)

The language of the document's text. When set, OCR will not auto-detect each page's language. It will assume the language given.

Leave it unset when a scanned document may mix languages, or when you do not know what the language is. Each page's language is then determined on its own, which is what lets a mixed document work without configuration.

Set it if you are certain every page is in the same language, including a Latin-alphabet one. Automatic handling is a prediction and is never certain, so naming the language takes that prediction out of the picture, and with it any chance of a page being treated as the wrong language.

For Arabic and Thai, setting it is the only way to get the handling built for them. They are recognized only when declared on the request, so such a document sent without language is still processed, but by general-purpose OCR, and may extract less accurately.

Accepted values: arabic, chinese_simplified, chinese_traditional, english, french, german, hebrew, italian, japanese, korean, portuguese, russian, spanish, thai. Any other value is rejected with 400.

This affects OCR only. It has no effect on a page whose text is read natively, or on an instance running with OCR disabled.

pages

string or null Default: null (every page)

Which pages to extract, as a comma-separated list of 1-indexed page numbers and ranges, for example "1-5,7,9-10". Left unset, the whole document is extracted. Only the selected pages are processed, so a selection reduces extraction time.

List pages in ascending order, with no page repeated. Extracted pages are always returned in document order, so a selection such as "5,2" or "1-5,3" is rejected rather than reordered or deduplicated. A range must run from low to high ("1-3", not "3-1").

A selection naming a page the document does not have returns 422 extract_invalid_page_indices, whose message gives the document's real page count. A selection that cannot be read as a page list returns 400.

return_absolute_pdf_page_numbers

bool Default: false

By default the page index on each extracted element is a 0-based position within the pages that were extracted. Set this to true to get instead the 1-based page number the page has in the source PDF.

This matters most alongside pages. Extracting "2,7" reports page numbers 2 and 7 with this enabled, rather than 0 and 1.

Response codes:

CodeBodyMeaning
200{"result": {...}, "processing_time_seconds": N}Extraction succeeded.
400{"error": "<message>"}Invalid request, rejected before extraction. Returned when: no document field in the form, the uploaded file is empty, the options field is not valid JSON, an unrecognized option or form field is provided, an option has the wrong type (e.g. string instead of boolean), the output_format or language value is not one of the allowed values, or the pages value format is incorrect.
413{"error": "Request payload exceeds N MB limit"}The uploaded file exceeds the maximum allowed size (default 500 MB, configurable via MAX_CONTENT_LENGTH_MB env var).
422{"error": {"category": "...", "code": "...", "message": "..."}}The request was well-formed but the document could not be processed (e.g. not a valid PDF, password-protected, or requires OCR on an OCR-disabled instance). category and code are stable, machine-readable strings you can branch on; message is a human-readable sentence for codes that warrant one and null otherwise. See 422 error codes below.
429{"error": "Server is busy processing another request"}The server is already processing another extraction. The server handles one extraction at a time. Retry after a delay or scale horizontally.
500{"error": "<message> [error_id=<uuid>]"}An internal error occurred during extraction. The body is a single generic message that never contains internal detail, followed by an opaque error_id. The server logs the failure at ERROR tagged with the same error_id, so the details can be given to Kensho support if needed.

422 error codes

A 422 body is a JSON object, {"error": {"category": "<category>", "code": "<code>", "message": <string|null>}}. The code is the stable identifier to branch on; category groups related codes; message is a human-readable sentence for the codes that warrant extra guidance (currently requires_ocr, ocr_page_size_too_large, ocr_not_available, and extract_invalid_page_indices) and null for the rest.

categorycodeMeaningWhat to do
bad_documentextract_invalid_pdfThe file could not be parsed as a valid PDF.Verify the upload is a real, uncorrupted PDF.
bad_documentextract_encrypted_pdfThe PDF is password-protected.Supply a decrypted copy.
bad_documentocr_bad_documentThe document could not be processed even with OCR; it may be corrupted, empty, or unsupported.Confirm the document opens and has renderable pages.
bad_documentocr_page_size_too_largeA page that needs OCR is too large to process. Either it exceeds MAX_OCR_PAGE_MEGAPIXELS if you have set it, or it is too long on a single side, which is always rejected whatever the page's area. The whole document is rejected, not just that page. Where MAX_OCR_PAGE_MEGAPIXELS is what rejected it, the message names the page, its size, and the limit it exceeded. The single-side rejection names no page, only what to do about it.Split or downscale the offending page and resubmit, or raise MAX_OCR_PAGE_MEGAPIXELS if the container has memory for it. Where the message names no page, the offender is the page with the longest single dimension. See Very large scanned pages.
client_errorrequires_ocrThe document has pages with no extractable text (e.g. scanned or image-based) and this instance runs with OCR disabled. Carries a message spelling this out.Enable OCR (ENABLE_DOCUMENT_OCR=true; see OCR for Scanned Documents), or send a text-based PDF.
client_errorocr_not_availableThe request set ocr to true, but this instance runs with OCR disabled. Carries a message saying so.Enable OCR (ENABLE_DOCUMENT_OCR=true, which is the default), or drop ocr from the request.
client_errorextract_invalid_page_indicesThe pages selection names a page the document does not have. Carries a message giving the document's page count.Resubmit with a selection inside the document's real page range.

New codes may be added over time, so treat an unrecognized code as a generic bad_document or client_error failure rather than erroring on it.

Quick Start (curl)

# Basic extraction
curl -F "document=@report.pdf" http://localhost:8000/extract
 
# With options
curl -F "document=@report.pdf" \
     -F 'options={"output_format":"structured_document","figure_extraction":false}' \
     http://localhost:8000/extract
 
# Health check
curl http://localhost:8000/health

Common Usage Patterns

Some common ways the container is deployed. This is neither an exhaustive list nor a set of recommendations.

  1. Sidecar, called synchronously. The container runs alongside your application and is called over localhost while handling a request or job. The least moving parts when your application already controls when documents arrive.
  2. Sidecar behind an async queue. Your application consumes jobs from a broker such as RabbitMQ, Kafka, or SQS, posts each document to the local container, and publishes the result. Gives you retries and backpressure.
  3. Shared internal service. One or more instances called over the network by several of your services. Needs timeouts raised at every hop, since extractions can run for many minutes.
  4. Autoscaled pool. Replicas scaled on queue depth or request volume, for example with KEDA on Kubernetes. Each instance handles one extraction at a time, so replica count is the main throughput lever.

Deployment Requirements

These apply to every pattern above, not just the examples that follow.

The container needs a writable temp directory

The server writes temporary files while loading models and needs a writable temp directory (default /tmp). If you run with a read-only root filesystem, mount a writable volume at /tmp or point TMPDIR at one. On Kubernetes that means an emptyDir (ideally medium: Memory) when readOnlyRootFilesystem: true is set. Without a writable temp directory the server cannot finish loading models, and /health never becomes ready.

Extractions outlive most default timeouts

Extractions can run for many minutes, so every intermediary between your application and the container needs timeouts long enough to match. Load balancers, ingress controllers, service meshes, and API gateways commonly default to 30-120 seconds and will kill the connection mid-extraction. Co-locating the container with your application sidesteps this entirely; otherwise raise the timeouts at each hop.

Deployment Example: Kubernetes Sidecar

One option is to run the extraction server as a sidecar in the same K8s pod as your application. Both containers share a network namespace, so your service calls localhost:8000/extract and no traffic leaves the pod.

The resource values and tuning variables below are illustrative. Size them for your own document mix, throughput targets, and platform. Memory is the one value worth checking first if you are upgrading from v3.6.0: it needs to cover the peak while OCR'ing scanned pages. See Resource Tuning.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: extract
  namespace: extract
spec:
  replicas: 3
  selector:
    matchLabels:
      app: extract
  template:
    metadata:
      labels:
        app: extract
    spec:
      # Must be >= GUNICORN_GRACEFUL_TIMEOUT so in-flight extractions
      # finish before K8s sends SIGKILL.
      terminationGracePeriodSeconds: 1800
      containers:
      - name: your-app
        image: your-org/your-service:latest
        # Your service calls http://localhost:8000/extract
      - name: extract
        image: 208007848330.dkr.ecr.us-east-1.amazonaws.com/kensho-containerized-extract:X.Y.Z
        imagePullPolicy: Always
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 60
          periodSeconds: 10
        # A liveness probe on /health is safe: the endpoint stays
        # responsive while an extraction is in progress. Keep
        # failureThreshold generous so only a truly stuck instance is
        # restarted.
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 60
          periodSeconds: 30
          timeoutSeconds: 10
          failureThreshold: 5
        resources:
          requests:
            memory: "6Gi"
            cpu: "2"
          limits:
            memory: "8Gi"
            cpu: "4"
        env:
        # MAX_DOCUMENT_OCR_WORKERS caps how many pages are OCR'd at once. It is both
        # the OCR throughput control and the main control over peak memory.
        # OMP_NUM_THREADS sizes the analysis stage that runs after OCR and has no
        # effect on OCR. Both are set here for the 4-core limit above. Leaving
        # OMP_NUM_THREADS unset lets the inference library size itself from the host's
        # core count, which ignores this container's CPU limit. Memory needs rise
        # with scanned documents; see Resource Tuning.
        - name: OMP_NUM_THREADS
          value: "2"
        - name: MAX_DOCUMENT_OCR_WORKERS
          value: "2"
        - name: GUNICORN_TIMEOUT
          value: "1800"
        - name: GUNICORN_GRACEFUL_TIMEOUT
          value: "1800"

Waiting for the Server at Startup

The extraction server takes 10-20 seconds to load ML models before it can serve requests. If your application calls /extract before loading completes, the response will hang until models finish loading.

The first request can block while models load

Poll /health on startup before sending extraction requests. A call to /extract during model loading does not fail, it blocks, so an unprepared first request looks like a 20-second stall or an unresponsive container.

import time
import requests
 
def wait_for_extract_server(timeout: int = 120) -> None:
    """Block until the extraction server is ready."""
    for _ in range(timeout):
        try:
            resp = requests.get("http://localhost:8000/health", timeout=2)
            if resp.status_code == 200:
                return
        except (requests.ConnectionError, requests.Timeout):
            pass
        time.sleep(1)
    raise RuntimeError("Extraction server not ready")

Call this once at your application's startup before sending your first extraction request.

Deployment Example: Docker Compose

For local development and testing, use Docker Compose with network_mode so both containers share a network namespace:

services:
  extract-server:
    image: 208007848330.dkr.ecr.us-east-1.amazonaws.com/kensho-containerized-extract:X.Y.Z
    platform: linux/amd64
    ports:
      - "8000:8000"
    environment:
      GUNICORN_TIMEOUT: "1800"
      GUNICORN_GRACEFUL_TIMEOUT: "1800"
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
      interval: 10s
      timeout: 5s
      retries: 30
      start_period: 120s
 
  your-app:
    image: your-org/your-service:latest
    network_mode: "service:extract-server"
    depends_on:
      extract-server:
        condition: service_healthy

OCR for Scanned Documents

The container runs local OCR by default. The server detects pages with no extractable text (scanned or image-based pages, or pages whose fonts don't map to readable characters) and runs OCR on them. Pages that already have native text are unaffected. OCR is only applied where needed.

OCR supports Latin-alphabet languages along with Chinese, Japanese, Korean, Cyrillic languages such as Russian, Hebrew, Arabic, and Thai. Nothing needs configuring for most of them, because each page is handled on its own, so a document that mixes languages works as it is. A document in a language outside that list is still processed, with no loss of support relative to v3.6.0. See the language option for the two languages that are the exception, and for when declaring a language is worth it.

To turn OCR off, set ENABLE_DOCUMENT_OCR=false. With OCR disabled, a document that needs OCR (because some of its pages have no extractable text) cannot be processed, and the request fails with a 422 response explaining that OCR is disabled. PDFs with native text are unaffected.

Configuration:

VariableDefaultDescription
ENABLE_DOCUMENT_OCRtrueRun local OCR on pages that lack native text. Set to false to disable; requests that require OCR then fail with a 422.
MAX_DOCUMENT_OCR_WORKERS6Number of parallel workers for OCR page processing. Each worker processes one page at a time, CPU-bound and on a single thread, so this is the OCR throughput control. It is also the main control over peak memory, because every page in flight holds memory for as long as it is being processed. Though there is a default, you should experiment and select your own. See Resource Tuning.
MAX_OCR_PAGE_MEGAPIXELS0Reject a request whose OCR'd pages would rasterize past this many megapixels, with 422 ocr_page_size_too_large. 0, the default, enforces no limit. See Very large scanned pages.

Limitations:

  • OCR quality depends on scan resolution and document complexity. Dense tables or low-resolution scans may produce incomplete text.
  • Processing time increases proportionally with the number of scanned pages.
  • With language unset, a document in a language outside those listed at the start of this section is handled by general-purpose OCR and extracts as it did in v3.6.0, without the gains a listed one gets. That coverage is broader than the language values: any Latin-alphabet language counts, whether or not it is one you can name on a request (naming one you cannot is rejected with 400).
  • Peak memory while processing scanned documents is higher than in v3.6.0. See Resource Tuning.
  • A page far larger than a normal document page can exhaust the container's memory. This depends on your configuration. See Very large scanned pages.

Very large scanned pages

A very large scanned page can exhaust the container's memory

Before text recognition, a scanned page is converted to an image, and that image's memory grows with the page's area. If the container runs out of memory, it is OOM-killed and restarted. The request in flight is dropped with no error response, and the container has to reload its models before it serves again.

We recommend setting MAX_OCR_PAGE_MEGAPIXELS if your documents can contain unusually large scanned pages. A request with a page over that many megapixels is then rejected immediately with a 422 ocr_page_size_too_large instead of risking the container, and the response names the page, its size and the limit it exceeded.

MAX_OCR_PAGE_MEGAPIXELS defaults to 0, which enforces no limit, because the right value depends on the memory you give the container, and on MAX_DOCUMENT_OCR_WORKERS, which is how many page images are held at once. Start from a page size you know your deployment handles. A page's area in megapixels is width_inches * height_inches * 90000 at the resolution the container uses, so an 8.5 x 11 inch page is 8.4 MP and a 36 x 48 inch engineering drawing is about 155 MP.

The limit is per page, so several pages just under it, processed together, can still exhaust memory. Pages with selectable text are never converted to images, so native PDFs are unaffected. Separately, a page can be too long on a single side to OCR whatever its area, and such a page is always rejected with the same 422, whatever you set here.

Resource Tuning

The container is CPU-bound. CPU allocation, OMP_NUM_THREADS, and MAX_DOCUMENT_OCR_WORKERS all compete for the same cores and should be tuned together. Each OCR worker runs as a separate process, so MAX_DOCUMENT_OCR_WORKERS sets how many pages are OCR'd at once, while OMP_NUM_THREADS governs the layout, table, and figure analysis that runs after OCR completes. OMP_NUM_THREADS has no effect on OCR, so the worker count alone sets OCR throughput. Because those stages are sequential, raising one does not speed up the other. Allocating more concurrent work than you have cores available can reduce throughput rather than increase it.

Memory. Scanned documents need more memory than in v3.6.0, so an allocation that was comfortable before may no longer be. Peak memory scales with MAX_DOCUMENT_OCR_WORKERS, since every page in flight holds memory while it is processed, so fewer workers means a lower peak. How much you need depends on page dimensions and how many pages need OCR, so verify it against a representative sample of your own documents. A single unusually large page can exceed whatever you settle on, which is what MAX_OCR_PAGE_MEGAPIXELS is for (see Very large scanned pages). Documents with no scanned pages are unaffected.

Two illustrative cases, assuming an 8-core allocation:

  • Mostly native PDFs with dense tables and figures. Few pages reach OCR, so most time goes to analysis. Favoring threads (for example OMP_NUM_THREADS=4 with 2 OCR workers) puts cores where the work is.
  • Mostly scanned documents. Every page goes through OCR, and page-level parallelism scales well across processes. Favoring workers (for example 4 OCR workers with OMP_NUM_THREADS=2) shortens the dominant phase. Watch memory as you raise the worker count: this is the workload where peak usage is highest.

These are illustrations rather than recommendations. Measure a representative sample of your own documents, comparing the processing_time_seconds field in each successful response, and tune from there.

Set OMP_NUM_THREADS explicitly

Left unset, the inference library sizes its thread count from the host's core count, ignoring any CPU limit applied to the container. On a large node that means heavy oversubscription with no error and no obvious symptom beyond slower extractions. It sizes the analysis stage only, so setting it will not change OCR throughput. Use MAX_DOCUMENT_OCR_WORKERS to tune OCR throughput.

Timeout Configuration

The server processes one extraction at a time. Extractions can take seconds (small documents) to tens of minutes (large documents with many tables and figures). Configure timeouts to match your expected workload.

SettingDefaultDescription
GUNICORN_TIMEOUT1800Seconds the server may be unresponsive before the worker is restarted. This is a liveness check, not a cap on extraction time: a long extraction keeps the server responsive and does not trip it on its own.
GUNICORN_GRACEFUL_TIMEOUT1800Seconds to finish in-flight requests after SIGTERM
Orchestrator shutdown grace period-However your runtime controls the time between SIGTERM and SIGKILL (e.g., terminationGracePeriodSeconds in Kubernetes), set it >= GUNICORN_GRACEFUL_TIMEOUT so in-flight extractions can finish.
HTTP client timeout-Your responsibility; should match expected extraction time

GUNICORN_GRACEFUL_TIMEOUT, your orchestrator's grace period and your HTTP client timeout all need to track your document sizes and extraction times. Start with the defaults and adjust based on observed performance.

Nothing in the container caps how long a single extraction may run, so your HTTP client timeout is the effective bound. GUNICORN_TIMEOUT does not serve that purpose: raising it above the default only delays restarting a worker that has genuinely stopped responding.

Too short a shutdown grace period discards in-flight extractions

If your runtime kills the container before GUNICORN_GRACEFUL_TIMEOUT elapses, any extraction still running is lost. On Kubernetes that setting is terminationGracePeriodSeconds, whose 30-second default sits far below the container's 1800-second graceful timeout, so the default alone discards in-flight work. This happens on every deploy and scale-down, and nothing in the logs identifies it as lost work.

Configuration Reference

Every environment variable the container reads, with the value it uses when you set nothing. All of them are optional. The linked sections carry the full guidance; this table is the index.

VariableDefaultPurposeDetails
ENABLE_DOCUMENT_OCRtrueWhether OCR runs on pages that lack native text. Set false and a document needing OCR fails with a 422.OCR for Scanned Documents
MAX_DOCUMENT_OCR_WORKERS6How many pages are OCR'd at once. Sets OCR throughput, and is the main control over peak memory.OCR for Scanned Documents, Resource Tuning
MAX_OCR_PAGE_MEGAPIXELS0Rejects a request whose OCR'd pages would rasterize past this many megapixels, with a 422 ocr_page_size_too_large. 0, the default, enforces no limit.Very large scanned pages
OMP_NUM_THREADSunsetThreads for the layout, table and figure analysis. Worth setting explicitly: left unset it sizes itself from the host's core count and ignores the container's CPU limit.Resource Tuning
MAX_CONTENT_LENGTH_MB500Largest upload accepted, in megabytes. A larger one is rejected with a 413.Endpoints
GUNICORN_TIMEOUT1800Seconds the server may be unresponsive before the worker is restarted. This is a liveness check, not a cap on extraction time: a long extraction keeps the server responsive and does not trip it on its own.Timeout Configuration
GUNICORN_GRACEFUL_TIMEOUT1800Seconds to finish in-flight requests after SIGTERM.Timeout Configuration

Two further timeout settings are not container configuration but still have to line up with it: your orchestrator's shutdown grace period and your HTTP client's timeout. Both are covered in Timeout Configuration.

Monitoring

The container logs to stderr. Use standard log aggregation:

# Example: Kubernetes
kubectl logs -n extract deployment/extract -f
 
# Example: Docker
docker logs -f <container-id>

The server logs every internal failure (500) at ERROR, tagged with the same error_id that appears in the 500 response body. Grep your logs for this error_id to find the details. Document-level failures (422) are logged at INFO, since they are expected outcomes rather than faults.

Calling from Your Service

import requests
 
response = requests.post(
    "http://localhost:8000/extract",
    files={"document": ("document.pdf", pdf_bytes, "application/pdf")},
    data={"options": '{"figure_extraction": true, "enhanced_table_extraction": true}'},
    timeout=1800,
)
 
if response.status_code == 200:
    result = response.json()["result"]
elif response.status_code == 429:
    # Server busy, retry after a delay
    ...
elif response.status_code == 422:
    # The document could not be processed. Branch on the machine-readable code.
    err = response.json()["error"]
    code = err["code"]  # e.g. "extract_invalid_pdf", "requires_ocr"
    ...
else:
    # 400 (bad request) or 500 (internal error). Here "error" is a string. On a 500 it ends with
    # an "[error_id=<uuid>]" token worth capturing and quoting to Kensho support to correlate the
    # failure with the server-side logs.
    error = response.json()["error"]
    ...

Concurrency

The server handles one extraction at a time

A request arriving while another extraction is in progress is rejected with HTTP 429, not queued. Clients that assume ordinary HTTP concurrency will drop documents under load unless they retry on 429 or keep to one in-flight request per instance.

To increase concurrency, scale horizontally.