> ## 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 markpdf-cpp.

# Streaming and asynchronous jobs

As in the rest of the SDKs, there are two different mechanisms:

1. **Response Streaming** (`/convert/stream`, `/convert/stream-from-url`): Progressive Markdown fragments via callback.
2. **Jobs due to saturation** (`202`): the request is queued when all backends are busy. See [`GET /jobs/{id}`](/docs/api/jobs).

## Streaming with `convertStream`

`markpdf-cpp` does not expose coroutines by default (to maintain C++17 compatibility); streaming is modeled with a callback invoked in the same thread for the duration of the underlying HTTP request (implemented with `CURLOPT_WRITEFUNCTION`):

```cpp theme={null}
#include <markpdf/client.hpp>
#include <iostream>

markpdf::Client client("YOUR_API_KEY");

markpdf::StreamOptions opts;
opts.filename = "report.pdf";
opts.slim = true;

client.convertStream("report.pdf", markpdf::StreamSource::File,
    [](std::string_view chunk) {
        std::cout << chunk;
    },
    opts);
```

<CodeGroup>
  ```cpp Local file theme={null}
  client.convertStream("report.pdf", markpdf::StreamSource::File, onChunk, opts);
  ```

  ```cpp Desde URL theme={null}
  opts.filename.clear();
  client.convertStream(
      "https://bucket.example.com/report.pdf?sig=...",
      markpdf::StreamSource::Url, onChunk, opts);
  ```

  ```cpp Acumulando en un buffer theme={null}
  std::string full;
  client.convertStream("report.pdf", markpdf::StreamSource::File,
      [&full](std::string_view chunk) { full.append(chunk); },
      opts);
  // `full` contains the entire Markdown at the end of the call
  ```
</CodeGroup>

### Run in a separate thread (do not block the main thread)

```cpp theme={null}
#include <thread>

std::thread worker([&client, &opts]() {
    client.convertStream("report.pdf", markpdf::StreamSource::File,
        [](std::string_view chunk) { publish(chunk); },
        opts);
});
worker.join();
```

`Client` is safe to use from another thread as long as you don't share the same call in progress between threads.

### `StreamSlimStrategy`

```cpp theme={null}
opts.strategy = markpdf::StreamSlimStrategy::Sampled; // default
```

* `Off`: pure streaming per page, without noise detection. TTFB minimum.
* `Sampled` (default): Sample the first few pages, 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 (`opts.autoPoll = true`), `convertFile` and `convertFromUrl` block the thread that calls them until the job finishes:

```cpp theme={null}
markpdf::ConvertOptions opts; // autoPoll = true por default
auto result = client.convertFile("report.pdf", opts); // puede tardar más si hubo 202
```

### Poll manual

```cpp theme={null}
markpdf::ConvertOptions opts;
opts.autoPoll = false;

auto result = client.convertFile("report.pdf", opts);
if (!result && result.error().kind == markpdf::ErrorKind::JobQueued) {
    std::string jobId = result.error().jobId.value();
    markpdf::JobStatus status;
    do {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        auto poll = client.getJob(jobId);
        if (!poll) throw std::runtime_error(poll.error().detail);
        status = poll.value();
    } while (status.status == markpdf::JobState::Queued ||
             status.status == markpdf::JobState::Processing);

    if (status.status == markpdf::JobState::Completed) {
        std::string markdown = status.body.value();
    } else {
        throw std::runtime_error(status.error.value());
    }
}
```

<Tip>
  Under normal load conditions you won't see `202` — it only happens when all backends are saturated. `autoPoll = true` (the default) already covers it transparently, included in the thread that makes the call.
</Tip>

<Warning>
  Results for completed jobs expire in approximately 1 hour. If you save a `jobId` for later and `getJob` returns `404` (mapped to `MarkpdfError` with `statusCode == 404`), resend the original conversion.
</Warning>
