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

# Streaming and asynchronous jobs

> Consume /convert/stream and handle 202 (auto-async) with Python's SDK.

# Streaming and asynchronous jobs

There are two different "async" mechanisms in API, and SDK handles them separately:

1. **Response Streaming** (`/convert/stream`, `/convert/stream-from-url`): The server outputs the Markdown in progressive chunks while converting, to lower the TTFB in large documents.
2. **Saturation Jobs** (`202`): When all backends are busy, any conversion endpoint queues the request and responds `202` with a `job_id` to query later. See [`GET /jobs/{id}`](/docs/api/jobs).

## Streaming with `convert_stream`

```python theme={null}
import markpdf

client = markpdf.Client(api_key="YOUR_API_KEY")

for chunk in client.convert_stream("informe-grande.pdf"):
    print(chunk, end="", flush=True)
```

`convert_stream` returns a `Iterator[str]` iterator; each element is a Markdown fragment, not necessarily a complete line or page. Concatenate them in order to reconstruct the document.

<CodeGroup>
  ```python Local file theme={null}
  for chunk in client.convert_stream("report.pdf", slim=True):
      handle(chunk)
  ```

  ```python Desde URL theme={null}
  for chunk in client.convert_stream(url="https://bucket.example.com/report.pdf?sig=...", slim=True):
      handle(chunk)
  ```

  ```python Async theme={null}
  async with markpdf.AsyncClient(api_key="YOUR_API_KEY") as client:
      async for chunk in client.convert_stream("report.pdf"):
          handle(chunk)
  ```
</CodeGroup>

### `stream_slim_strategy`

Controls how repeated headers/footers are cleaned up without sacrificing too much TTFB:

```python theme={null}
client.convert_stream("report.pdf", slim=True, stream_slim_strategy="sampled")
```

* `"off"`: pure streaming per page, without noise detection. TTFB minimum.
* `"sampled"` (default): Sample the first few pages for noise, then output per page.
* `"full"`: materializes the entire document before issuing. Better cleaning, worse TTFB.

See [Parameters](/docs/api/parameters#streaming-only-parameters).

<Note />

## Jobs due to saturation (202)

By default, `convert_file` and `convert_from_url` handle `202` transparently:

```python theme={null}
# auto_poll=True (default): SDK waits and polls for you
markdown = client.convert_file("report.pdf")
```

Internally, if the server responds `202`, the SDK:

1. Read `job_id` and `retry_after_seconds` of the answer.
2. Sleep `retry_after_seconds` (5s by default).
3. Llama `GET /jobs/{job_id}`.
4. Repite mientras `status` sea `"queued"` o `"processing"`.
5. Returns `body` when `status == "completed"`, or throws `MarkpdfJobFailedError` if `status == "failed"`.

### Poll manual

If you prefer to control the loop yourself (for example, to show progress in a UI), disable auto-poll:

```python theme={null}
from markpdf import MarkpdfJobQueuedError

try:
    markdown = client.convert_file("report.pdf", auto_poll=Failedse)
except MarkpdfJobQueuedError as job:
    print(f"En cola: {job.job_id}")
    while True:
        status = client.get_job(job.job_id)
        if status.status == "completed":
            markdown = status.body
            break
        if status.status == "failed":
            raise RuntimeError(status.error)
        time.sleep(job.retry_after_seconds)
```

<Tip>
  Under normal load conditions you will never see `202` — it only happens when all backends are saturated. You don't need to design your application assuming it will always happen; `auto_poll=True` (the default) already covers it without additional code.
</Tip>

<Warning>
  Results for completed jobs expire in approximately 1 hour. If you save `job_id` for later reference and receive `404`, resend the original conversion.
</Warning>
