> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lazycloud.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Extract text from PDFs and images

> Deploy a browser upload app that runs OCR in the background and returns extracted text.

FastAPI serves the upload page and API, and Tesseract runs in a separate
background function. Files pass between them through a volume. The app accepts
PDF, JPEG, and PNG files.

Source is in `document_processing/`.

The CLI installs once with `uv tool install lazycloud-client`. The commands
below download a standalone project and install its dependencies, with no
repository checkout. The rest of the guide runs from that directory through
`uv run`, which uses the project's pinned SDK.

```bash theme={null}
lazycloud example download document-processing
cd document-processing
uv sync
uv run lazycloud login
```

## Prepare the app

`uv sync` installs FastAPI from `pyproject.toml` so the CLI can import the
application. Tesseract and Poppler install only in the remote image.

The app declares its shared volume in `resources.py`:

```python theme={null}
from lazycloud import Volume

data_volume = Volume("document-processing-data", "/document-processing")
```

Both workloads include `volumes=[data_volume]`, and LazyCloud creates or
reuses the volume when they start. The secret that signs job tokens needs an
initial value. Run the example's Python setup module once:

```bash theme={null}
uv run python -m document_processing.configure
```

`configure.py` calls `Secret(JOB_TOKEN_SECRET_NAME).create(...)` with a random
value and never prints it. It refuses to replace an existing secret, so if you
already configured this workspace, skip to deployment. The signing secret
protects individual results, and only the HTTP service receives it.

<Warning>
  This example's upload URL is public. Anyone with the URL can submit work
  that uses your compute. Use test documents, stop the app when finished, and
  add user authentication and upload rate limits before offering it to users.
</Warning>

## Deploy and upload

```bash theme={null}
uv run lazycloud deploy document_processing.app:app
```

Open the `document-api` URL from the deploy output and pick a document in the
upload form. The page submits it, shows job progress, and displays the
extracted text. The limits are 20 pages per PDF, 10 MiB per upload, and
200,000 characters per text result.

The worker removes the raw upload after processing, even when processing
fails. Click Delete result in the browser when you no longer need the text.
Results from abandoned jobs stay in the volume until you delete them.

## Call the API from a script

Set the URL without a trailing slash. These commands need curl and jq:

```bash theme={null}
export DOCUMENT_API_URL="<document-api-url>"
JOB_TOKEN="$(
  curl --fail --show-error \
    -X PUT "$DOCUMENT_API_URL/api/documents/sample.pdf" \
    -H "Content-Type: application/pdf" \
    --data-binary @sample.pdf |
  jq -er '.job_token'
)"
```

This uploads a local `sample.pdf` and saves the returned job token without
printing it. Read the status:

```bash theme={null}
curl --fail --show-error "$DOCUMENT_API_URL/api/jobs/status" \
  -H "X-Job-Token: $JOB_TOKEN"
```

Once status is `complete`, fetch the text:

```bash theme={null}
curl --fail --show-error "$DOCUMENT_API_URL/api/jobs/result" \
  -H "X-Job-Token: $JOB_TOKEN"
```

The result route returns HTTP 409 until the task finishes. If the task fails,
inspect its error before retrying the upload. The token lets its holder read
or delete that one job and expires after 24 hours. Keep it out of logs and
URLs.

Delete the result once your script has saved it:

```bash theme={null}
curl --fail --show-error -X DELETE "$DOCUMENT_API_URL/api/jobs" \
  -H "X-Job-Token: $JOB_TOKEN"
unset JOB_TOKEN
```

Deletion returns HTTP 409 while work is still running.

## Diagnose a failed upload or job

For an HTTP error, inspect `uv run lazycloud logs --deployment document-api -n 100`.
For OCR failures, use:

```bash theme={null}
uv run lazycloud task list --app document_processing --limit 20
uv run lazycloud task logs <task-id>
```

The API and worker definitions live in `app.py` and `worker.py`. To adapt the
app, change resource sizes and upload limits there and in `resources.py`, and
add a retention job for abandoned results. Rotating
`DOCUMENT_JOB_TOKEN_SECRET` invalidates every outstanding job token.

## Clean up

Delete the two deployments to stop accepting work:

```bash theme={null}
uv run lazycloud deployment delete document-api
uv run lazycloud deployment delete ocr-worker
```

If you created the volume and secret for this example, download any results
you need before removing them:

```bash theme={null}
uv run lazycloud volume delete document-processing-data
uv run lazycloud secret delete DOCUMENT_JOB_TOKEN_SECRET
unset DOCUMENT_API_URL
```
