Guides

Async Polling

When a PDF is too large for synchronous processing or you explicitly request async mode, PDFPipe queues the job and returns a poll URL. This guide explains the flow and shows how to implement it.

When does async processing happen?

Async processing is used when either:

  • The PDF file is 10 MB or larger
  • You set "async": true in the request body
  • Synchronous processing exceeds your timeout (1–60 seconds) — the API queues the job and may include a message on the 202 response
  • You use the batch endpoint (always async)

The API returns 202 Accepted instead of 200, with a pollUrl you can use to check progress.

The async flow

1. Submit the request

curl - async request
curl -X POST https://api.pdfpipe.dev/v1/convert \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/large-report.pdf",
    "format": "json",
    "async": true
  }'

2. Receive the poll URL

202 - Accepted
{
  "requestId": "req_01J9X7K2M...",
  "status": "pending",
  "pollUrl": "/v1/status/req_01J9X7K2M..."
}

If you set webhook on the convert request, the body may also include webhook.url and webhook.secret. After a timeout-driven fallback to the queue, a message field may explain why the job is async.

202 - With webhook / message (optional)
{
  "requestId": "req_01J9X7K2M...",
  "status": "pending",
  "pollUrl": "/v1/status/req_01J9X7K2M...",
  "webhook": { "url": "https://example.com/hooks/pdfpipe", "secret": "whsec_..." },
  "message": "Example when the job was queued after exceeding your timeout."
}

3. Poll for status

Make GET requests to /v1/status/:requestId every 5 seconds (append ?returnMethod=inline if you want content in the JSON when status is complete). The status progresses through:

pendingprocessingcomplete
Still processing
{
  "requestId": "req_01J9X7K2M...",
  "status": "processing",
  "format": "json",
  "type": "inline",
  "createdAt": "2026-02-22T11:00:00.000Z",
  "updatedAt": "2026-02-22T11:00:02.500Z"
}
Complete - result ready
{
  "requestId": "req_01J9X7K2M...",
  "status": "complete",
  "format": "json",
  "type": "inline",
  "detectedType": "inline",
  "pagesProcessed": 48,
  "creditsUsed": 1,
  "resultUrl": "https://pdfpipe-results.s3...",
  "expiresAt": "2026-02-23T11:00:00.000Z",
  "processingDurationMs": 12450,
  "createdAt": "2026-02-22T11:00:00.000Z",
  "updatedAt": "2026-02-22T11:00:12.450Z"
}

4. Handle failures

If the status is failed, the response includes an error object with a code, message, and optional suggestion:

Failed response
{
  "requestId": "req_01J9X7K2M...",
  "status": "failed",
  "format": "json",
  "type": "inline",
  "error": {
    "code": "PROCESSING_ERROR",
    "message": "Processing failed. Please retry or contact support.",
    "suggestion": "Ensure the URL is publicly accessible and returns a valid PDF."
  },
  "createdAt": "2026-02-22T11:00:00.000Z",
  "updatedAt": "2026-02-22T11:00:08.000Z"
}

Timeout instead of guessing sync vs async

Set timeout to the maximum seconds (1–60) you are willing to wait inline. If the converter cannot finish in that window, you receive 202 and poll (or use a webhook) as usual. This avoids choosing between sync and async up front for borderline files.

Webhooks (no polling)

Pass webhook: { url, secret? } on POST /v1/convert. When the job is asynchronous, the 202 body includes webhook.url and webhook.secret (generated if you omitted secret). PDFPipe POSTs to your HTTPS endpoint when processing completes; verify payloads with HMAC-SHA256 using the X-PDFPipe-Signature: sha256=... header (see the API Reference).

Polling best practices

  • Poll every 5 seconds. Most conversions complete within 10–30 seconds.
  • Set a timeout. Stop polling after 5 minutes (60 attempts) and treat it as a failure.
  • Check for terminal states. Stop polling when status is complete or failed.
  • Download the result promptly. The resultUrl is a presigned link; each signed URL expires after a tier-based TTL, and the API stops issuing new URLs after your plan's result retention window from request creation (1 hour to 30 days).

Full Node.js example

Node.js / TypeScript
async function convertPdf(apiKey: string, pdfUrl: string) {
  // 1. Submit the request
  const res = await fetch("https://api.pdfpipe.dev/v1/convert", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ url: pdfUrl, format: "json", async: true }),
  });

  const data = await res.json();

  // 2. If synchronous, return immediately
  if (res.status === 200 && data.resultUrl) {
    return data;
  }

  // 3. Poll until complete or failed
  const pollUrl = `https://api.pdfpipe.dev/v1/status/${data.requestId}`;
  const maxAttempts = 60;

  for (let i = 0; i < maxAttempts; i++) {
    await new Promise(r => setTimeout(r, 5000));

    const poll = await fetch(pollUrl, {
      headers: { "Authorization": `Bearer ${apiKey}` },
    });
    const status = await poll.json();

    if (status.status === "complete") return status;
    if (status.status === "failed") throw new Error(status.error.message);
  }

  throw new Error("Polling timed out");
}