# PDF to Markdown in Python: Submit, Resume, and Read the Result

Run PDF Craft's Python example, follow an asynchronous conversion, resume a saved task, read the Markdown ZIP, and adapt the workflow for your own PDFs.

Converting scanned PDFs to Markdown in Python lets you combine document recognition, downloading, and text processing into a single workflow. PDF Craft handles conversions in the cloud: your Python script creates a task, uploads the PDF, waits for processing, and downloads a ZIP containing Markdown and image assets.

Start with the [official Python example](https://pdfcraft.ai/examples/api-quickstart.py). It uses a fixed one-page PDF to verify connectivity, authentication, and output downloads. To process your own documents, you will need to update the upload source and output validation. If you only need to convert a file manually, see the [web conversion guide](https://pdfcraft.ai/en/blog/pdf-to-markdown-guide/) instead.

## Run the fixed sample first

You need Python 3.9 or later, with no third-party dependencies. Create a production API key in the [OOMOL Console](https://console.oomol.com/api-key) and confirm that your account has enough credits. Running conversions incurs usage charges; see the [current rates](https://pdfcraft.ai/en/recharge/).

The following commands work in Bash and Zsh. Download the script, then enter your key without printing it to the terminal:

```bash
curl --fail --show-error --location \
  https://pdfcraft.ai/examples/api-quickstart.py \
  --output api-quickstart.py

export PDF_CRAFT_API_KEY="$(
  python3 -c 'import getpass; print(getpass.getpass("API key: "))'
)"
python3 api-quickstart.py
```

The script saves a state file named `pdf-craft-task.json`. Its `uuid` identifies the conversion so you can return to the same task later. When the run succeeds, the output is written to `pdf-craft-result.zip`. The script also checks the generated Markdown for a known sentence from the sample PDF to confirm it received the expected output.

This script only processes the built-in sample and does not include an `--input` option. To convert your own files, update the upload source and result validation as described below.

## Save the task ID and continue according to its status

Conversion involves distinct stages: create, upload, launch, query, and download. An accepted launch request means the service has accepted the task; you retrieve the result once processing finishes.

The script's main loop determines its next action based on the task status:

| Status                  | What the program does next                                                 |
| ----------------------- | -------------------------------------------------------------------------- |
| `waiting_for_upload`    | Requests upload URLs, uploads if needed, and submits conversion parameters |
| `running`               | Waits and queries again, gradually increasing the polling interval         |
| `succeeded`             | Requests a download URL, downloads the ZIP, and saves it                   |
| `failed` or `cancelled` | Stops and preserves the error information                                  |

If an upload has already finished, requesting upload URLs again returns read URLs for the file, so you do not need to re-upload it. Launching with `{"output_format":"markdown"}` and omitting a target language preserves the document's original language. If you need footnotes, configure refinement in your own code using the available [conversion parameters](https://pdfcraft.ai/en/docs/api/conversion/).

Send your API key only with API requests. Do not include it when uploading to or downloading from the storage URLs returned by the service. Upload requests must include the headers provided in the response. The [step-by-step cURL guide](https://pdfcraft.ai/en/docs/getting-started/quickstart/) shows the complete request sequence.

## Resume the same task after a timeout

A local timeout or connection failure does not necessarily mean the cloud conversion failed. Keep the state file and resume using the same account that created the task:

```bash
python3 api-quickstart.py \
  --resume pdf-craft-task.json \
  --output pdf-craft-result.zip \
  --timeout 1200
```

The `--timeout` flag sets the total wait time for this script execution, in seconds. It does not reset the processing timer for the cloud task. The script will not overwrite an existing output file, so specify a different `--output` path if needed.

If the state file contains `creation_unknown` without a `uuid`, check your [task list](https://pdfcraft.ai/en/docs/api/overview/#list-tasks) to confirm whether creation succeeded before creating another task. Relaunching a task with the `failed` status will not run it again. See the [task lifecycle guide](https://pdfcraft.ai/en/docs/api/lifecycle/) for recovery steps at each stage.

## Read the Markdown inside the ZIP with Python

You can inspect the downloaded archive without extracting every file to disk. This snippet uses only the Python standard library to list archived assets and preview the beginning of each Markdown file:

```python
from pathlib import Path
from zipfile import ZipFile

result = Path("pdf-craft-result.zip")
with ZipFile(result) as archive:
    for item in archive.infolist():
        if item.is_dir():
            continue
        print(f"{item.filename}: {item.file_size} bytes")
        if item.filename.lower().endswith(".md"):
            with archive.open(item) as source:
                preview = source.read(8192).decode("utf-8", errors="replace")
            print(preview)
```

This reads up to the first 8 KiB of each Markdown file. Longer documents will be truncated in the preview, and the final character may be incomplete. For downstream processing, read the full file contents according to your application's memory requirements and document sizes.

To edit or render the results locally, unpack the archive into a dedicated folder while preserving its directory structure. Moving only the `.md` file can break relative links to extracted images. Always keep the accompanying assets with the document when archiving or sharing it; see the [Markdown image resource guide](https://pdfcraft.ai/en/blog/pdf-markdown-image-assets/).

## Adapt the workflow for your own PDF

Once the sample runs successfully, you can reuse the core task loop and adapt the input and validation logic for your own pipeline:

* **Upload source:** Replace the step that downloads `SAMPLE_URL` with code that reads your local PDF. For example, `Path("document.pdf").read_bytes()` provides the payload to send to the task's upload URL. Plan your upload strategy and memory usage around the size of your files.
* **Result validation:** The sample's `EXPECTED_TEXT` check applies only to its one-page PDF. Replace it with checks for your expected Markdown files, then validate key sections or content as needed.
* **Task records:** Save the input file reference, `uuid`, and launch parameters together. When resuming, use the same file and parameters. Replacing a file that has already been uploaded requires creating a new task.
* **Size and time limits:** The example script caps each response read at 10 MiB and defaults to a 600-second timeout. These are settings in the example code, not general service file limits.

Start by testing with one of your actual documents. Save the source PDF, task record, and output ZIP before integrating the extraction step into downstream note systems or automated pipelines. This ensures you can resume after an interruption and inspect the generated Markdown against the original file.

