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

> Upload progress with createConvertStore, streaming with the base client, and 202 (auto-async).

# Streaming and asynchronous jobs

As in the rest of the framework SDKs, there are two layers: **upload progress** (own from the store, via `XMLHttpRequest`) and **streaming/jobs of the API** (inherited from the base client `MarkpdfClient`).

## Upload progress with the store

```svelte theme={null}
<script lang="ts">
  import { MarkpdfClient, createConvertStore } from "@markpdf/svelte";

  const client = new MarkpdfClient({ apiKey: "YOUR_API_KEY" });
  const convertStore = createConvertStore(client);
</script>

<input type="file" on:change={(e) => convertStore.convert(e.currentTarget.files[0])} />

{#if $convertStore.status === "uploading"}
  <progress value={$convertStore.progress} max={100} />
{/if}
```

`status` goes through `"idle" → "uploading" → "converting" → "success"|"error"`. `progress` only makes sense during `"uploading"`.

## Streaming Markdown with the base client

```svelte theme={null}
<script lang="ts">
  import { MarkpdfClient } from "@markpdf/svelte";

  const client = new MarkpdfClient({ apiKey: "YOUR_API_KEY" });
  let text = "";

  async function streamFrom(url: string) {
    text = "";
    for await (const chunk of client.convertStream({ url }, { slim: true })) {
      text += chunk;
    }
  }
</script>

<button on:click={() => streamFrom("https://bucket.example.com/report.pdf?sig=...")}>
  Convertir en streaming
</button>
<pre>{text}</pre>
```

### Streaming from a SvelteKit endpoint (`+server.ts`)

```ts src/routes/api/convert/stream/+server.ts theme={null}
import { MARKPDF_API_KEY } from "$env/static/private";
import { MarkpdfClient } from "@markpdf/svelte";
import type { RequestHandler } from "./$types";

const client = new MarkpdfClient({ apiKey: MARKPDF_API_KEY });

export const POST: RequestHandler = async ({ request }) => {
  const { url } = await request.json();

  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      for await (const chunk of client.convertStream({ url })) {
        controller.enqueue(encoder.encode(chunk));
      }
      controller.close();
    },
  });

  return new Response(stream, { headers: { "content-type": "text/markdown; charset=utf-8" } });
};
```

## Jobs due to saturation (202)

The store (`createConvertStore`) does not automatically poll jobs queued by `/convert` — if API responds `202`, that case remains `$convertStore.error`. For explicit handling, use the base client directly:

```ts theme={null}
const client = new MarkpdfClient({ apiKey: "YOUR_API_KEY" });

// base client convertFile DOES auto poll 202 (autoPoll: true by default)
const markdown = await client.convertFile(buffer, { filename: "report.pdf" });
```

See [Jobs due to saturation of SDK base](/docs/sdks/nodejs/streaming-and-async#jobs-por-saturación-202) for manual poll with `autoPoll: false`.

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