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

# Coroutines and asynchronous jobs

> Suspended nature of the client and handling of 202 (auto-async) with Kotlin's SDK.

# Coroutines and asynchronous jobs

All `MarkpdfClient` methods are `suspend fun`: they do not block the thread while waiting for the HTTP response, and they integrate naturally with `viewModelScope`, `lifecycleScope`, `Dispatchers.IO`, or any `CoroutineScope` of their own.

Additionally, there is a separate mechanism on the server: when all conversion backends are saturated, the API responds `202` with a `job_id` instead of blocking the request. See [`GET /jobs/{id}`](/docs/api/jobs).

## Basic use with `runBlocking`

For scripts or `main()`:

```kotlin theme={null}
fun main() = runBlocking {
    val client = MarkpdfClient(apiKey = "YOUR_API_KEY")
    val result = client.convertFile(File("report.pdf"))
    // ...
}
```

## From `ViewModel` (Android)

```kotlin theme={null}
class ConvertViewModel(private val client: MarkpdfClient) : ViewModel() {

    private val _markdown = MutableStateFlow<String?>(null)
    val markdown: StateFlow<String?> = _markdown

    fun convert(file: File) {
        viewModelScope.launch {
            val result = client.convertFile(file, ConvertOptions(mode = ConversionMode.FAST))
            if (result is ConvertResult.Markdown) {
                _markdown.value = result.markdown
            }
        }
    }
}
```

## Parallel conversions with `async`

```kotlin theme={null}
suspend fun convertAll(files: List<File>): List<ConvertResult> = coroutineScope {
    files.map { file ->
        async { client.convertFile(file) }
    }.awaitAll()
}
```

<Warning>
  Aggressively parallelizing requests can trigger `429`. Limit concurrency (for example with `Semaphore(n)` around each `async`) if you convert many documents at once.
</Warning>

## Jobs due to saturation (202)

By default, all conversion methods handle `202` transparently (`ConvertOptions.autoPoll` is `true`):

```kotlin theme={null}
// SDK waits and polls automatically, without blocking the thread
val result = client.convertFile(File("report.pdf"))
```

### Poll manual

```kotlin theme={null}
val result = client.convertFile(
    File("report.pdf"),
    ConvertOptions(autoPoll = false),
)

when (result) {
    is ConvertResult.Queued -> {
        println("En cola: ${result.job.jobId}")
        val finalJob = client.waitForJob(result.job.jobId, pollInterval = 5.seconds)

        if (finalJob.status == JobStatus.COMPLETED) {
            val markdown = finalJob.body?.jsonPrimitive?.content
        } else {
            error(finalJob.error ?: "job falló")
        }
    }
    is ConvertResult.Markdown -> println(result.markdown)
    is ConvertResult.Json -> {}
}
```

### Expose progress as `Flow`

```kotlin theme={null}
fun watchJob(client: MarkpdfClient, jobId: String): Flow<Job> = flow {
    var job = client.getJob(jobId)
    emit(job)
    while (job.status == JobStatus.QUEUED || job.status == JobStatus.PROCESSING) {
        delay(5.seconds)
        job = client.getJob(jobId)
        emit(job)
    }
}
```

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