> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.emtelligent.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.emtelligent.com/_mcp/server.

# Quickstart Code Example

The OCR API takes a file as a raw request body and returns layout-aware text.
This page builds up a working client in four steps; the complete script is at the
end.

You need an API key with the `ocr:process` scope, which you create on the
[emtelligent platform](/platform/api-keys). Set it in your environment rather than
putting it in code:

```bash
export OCR_API_KEY='...'
```

## Create a session

Authentication is an API key sent as a bearer token. Putting it on a
`requests.Session` means every call below carries it.

```python
import os
import requests

BASE = "https://ocr.emtelligent.com"

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['OCR_API_KEY']}"
```

## Submit a document

Post the file as the raw request body — not multipart, and not a JSON wrapper.
The format is detected from the file's own bytes, so the `Content-Type` header is
ignored; PDF, PNG, JPEG and TIFF are accepted. `X-Filename` sets the job's
display name and is optional.

```python
with open("report.pdf", "rb") as fp:
    response = session.post(
        f"{BASE}/jobs",
        data=fp,
        headers={"X-Filename": "report.pdf"},
    )
response.raise_for_status()
job_id = response.json()["job_id"]
```

## Wait for it to finish

A job moves through `queued` → `rendering` → `ocr` → `done`, or ends in
`failed`. Poll the status endpoint until it reaches a terminal state.

```python
import time

while True:
    status = session.get(f"{BASE}/jobs/{job_id}").json()
    if status["state"] in ("done", "failed"):
        break
    time.sleep(2)

if status["state"] == "failed":
    raise RuntimeError(status.get("error") or "OCR failed")
```

> **Tip**
>
> Add `?timings=true` to the status call to get per-page render and OCR
> durations, which is the quickest way to see where time goes on a large
> document.

## Read the result

```python
result = session.get(f"{BASE}/jobs/{job_id}/result").json()
```

Results auto-purge after retrieval or a short retention window, so read them
once and store what you need. To release the input file and rendered pages
immediately:

```python
session.delete(f"{BASE}/jobs/{job_id}")
```

## The whole thing

**`ocr_quickstart.py`**

```python title="ocr_quickstart.py"
import os
import time

import requests

BASE = "https://ocr.emtelligent.com"

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['OCR_API_KEY']}"


def ocr(path):
    """Submit one file and return its OCR result."""
    with open(path, "rb") as fp:
        response = session.post(
            f"{BASE}/jobs",
            data=fp,
            headers={"X-Filename": os.path.basename(path)},
        )
    response.raise_for_status()
    job_id = response.json()["job_id"]

    while True:
        status = session.get(f"{BASE}/jobs/{job_id}").json()
        if status["state"] in ("done", "failed"):
            break
        time.sleep(2)

    if status["state"] == "failed":
        raise RuntimeError(status.get("error") or "OCR failed")

    return session.get(f"{BASE}/jobs/{job_id}/result").json()


if __name__ == "__main__":
    print(ocr("report.pdf"))
```

## Next

#### [API Reference](/ocr/api)

Every operation, parameter and response field.

#### [Overview](/ocr/overview)

What the output contains — bounding boxes, reading order, tables.