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

# Asynchronous jobs

> Handle 202 (auto-async) with the Flutter/Dart package.

# Asynchronous jobs

When all conversion backends are saturated, API responds `202` with `job_id` and queues the request instead of failing or blocking the request. See [`GET /jobs/{id}`](/docs/api/jobs).

<Note>
  The `markpdf` package does not expose streaming fragments (`/convert/stream`) — it is intended for the typical flow of a mobile app, where you expect the complete result. If you need progressive streaming from your own backend, do it there with Node.js [SDK](/docs/sdks/nodejs/streaming-and-async) or [Python](/docs/sdks/python/streaming-and-async) and expose the final result to your Flutter app.
</Note>

## Automatic handling (`autoPoll: true`)

By default, `ConvertOptions.autoPoll` is `true` and all conversion methods (`convertFile`, `convertBytes`, `convertFromUrl`) transparently poll until the result is obtained:

```dart theme={null}
final result = await client.convertFile(file); // espera y hace poll automáticamente
```

## Poll manual

With `autoPoll: false`, the method returns `QueuedResult` instead of waiting:

```dart theme={null}
final result = await client.convertFile(
  file,
  options: const ConvertOptions(autoPoll: false),
);

switch (result) {
  case QueuedResult(:final job):
    print('En cola: ${job.jobId}');
    final finalJob = await client.waitForJob(
      job.jobId,
      pollInterval: const Duration(seconds: 5),
    );

    if (finalJob.status == JobStatus.completed) {
      final markdown = finalJob.body as String;
    } else {
      throw Exception(finalJob.error);
    }
  case MarkdownResult(:final markdown):
    print(markdown);
  case JsonConvertResult():
    break;
}
```

### Poll with `Stream` (to show progress in the UI)

A common pattern in Flutter apps is to expose the job status as `Stream<Job>` to update a `StreamBuilder`:

```dart theme={null}
Stream<Job> watchJob(MarkpdfClient client, String jobId) async* {
  Job job;
  do {
    job = await client.getJob(jobId);
    yield job;
    if (job.status == JobStatus.queued || job.status == JobStatus.processing) {
      await Future.delayed(const Duration(seconds: 5));
    }
  } while (job.status == JobStatus.queued || job.status == JobStatus.processing);
}
```

```dart theme={null}
StreamBuilder<Job>(
  stream: watchJob(client, jobId),
  builder: (context, snapshot) {
    final status = snapshot.data?.status;
    return switch (status) {
      JobStatus.queued => const Text('En cola...'),
      JobStatus.processing => const Text('Procesando...'),
      JobStatus.completed => const Text('Listo'),
      JobStatus.failed => const Text('Failed'),
      _ => const CircularProgressIndicator(),
    };
  },
);
```

<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 without additional code in most cases.
</Tip>

<Warning>
  Results for completed jobs expire in approximately 1 hour. If you save a `jobId` and receive `404` when querying it, resend the original conversion.
</Warning>
