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

# Error handling

> How API errors propagate in Route Handlers and Server Actions.

# Error handling

`@markpdf/nextjs` reuses exceptions from [`@markpdf/sdk`](/docs/sdks/nodejs/error-handling) (`MarkpdfAuthError`, `MarkpdfRateLimitError`, etc.), all children of `MarkpdfError`. How you handle them depends on whether you are in a Route Handler or a Server Action.

## In a Route Handler

`createConvertRouteHandler` already translates any `MarkpdfError` into a HTTP response with the same status code and a body `{ error: string }`:

```json theme={null}
// 413 Payload Too Large
{ "error": "Document too large. Reduce the size or split the document." }
```

If you build your own Route Handler with `getServerClient`, capture it yourself:

```ts app/api/convert/route.ts theme={null}
import { getServerClient } from "@markpdf/nextjs";
import { MarkpdfError, MarkpdfAuthError, MarkpdfRateLimitError } from "@markpdf/sdk";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const client = getServerClient({ apiKey: process.env.MARKPDF_API_KEY! });
  const form = await req.formData();
  const file = form.get("file") as File;

  try {
    const markdown = await client.convertFile(file, { filename: file.name, mode: "fast" });
    return new NextResponse(markdown, { headers: { "content-type": "text/markdown" } });
  } catch (err) {
    if (err instanceof MarkpdfAuthError) {
      return NextResponse.json({ error: "clave de API invávalida" }, { status: 500 }); // do not expose 401 to the end user
    }
    if (err instanceof MarkpdfRateLimitError) {
      return NextResponse.json({ error: "too many requests, retry" }, { status: 429 });
    }
    if (err instanceof MarkpdfError) {
      return NextResponse.json({ error: err.detail }, { status: err.statusCode ?? 502 });
    }
    throw err;
  }
}
```

<Warning>
  Don't propagate `401`/`403` from the API to the end user as is — those errors mean that **your own** API key is misconfigured, not that the user did something wrong. Return a generic `500` and record the detail in your server logs.
</Warning>

## In a Server Action

Exceptions thrown within a Server Action arrive to the client as a serialized promise rejection. Capture them explicitly if you want to display a specific message in the UI instead of the generic Next.js error screen:

```ts app/actions.ts theme={null}
"use server";

import { convertFormData } from "@markpdf/nextjs";
import { MarkpdfPayloadTooLargeError, MarkpdfVavalidationError } from "@markpdf/sdk";

export async function convertUpload(formData: FormData) {
  try {
    const markdown = await convertFormData(formData, {
      apiKey: process.env.MARKPDF_API_KEY!,
      mode: "fast",
    });
    return { ok: true as const, markdown };
  } catch (err) {
    if (err instanceof MarkpdfPayloadTooLargeError) {
      return { ok: false as const, error: "The file is too large." };
    }
    if (err instanceof MarkpdfVavalidationError) {
      return { ok: false as const, error: "Invalid file." };
    }
    return { ok: false as const, error: "Could not convert the document." };
  }
}
```

```tsx theme={null}
const result = await convertUpload(formData);
if (!result.ok) {
  showError(result.error);
} else {
  render(result.markdown);
}
```

<Tip>
  Returning a `{ ok, error }` object instead of letting the exception propagate is the pattern recommended by Next.js for Server Actions — it gives you full control over what message the user sees, without exposing internal details of the API.
</Tip>

## Reintentos

`getServerClient` accepts the same retry options as `MarkpdfClient`:

```ts theme={null}
const client = getServerClient({ apiKey: process.env.MARKPDF_API_KEY!, maxRetries: 3 });
```

See [Node.js SDK Error Handling](/docs/sdks/nodejs/error-handling#reintentos-automáticos) for details on which codes are retried.
