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 is an attachment (auto-detected or
type: "attachment") - headless browser jobs always run in the queue - The PDF file is 10 MB or larger, or the host does not report a Content-Length (unless you set
timeout) - You set
"async": truein the request body - Synchronous processing exceeds your
timeout(1-60 seconds) - the API queues the job and includes amessageon 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 -X POST https://api.pdfpipe.dev/v1/convert \
-H "Authorization: Bearer pk_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/large-report.pdf",
"format": "json",
"async": true
}'2. Receive the poll URL
{
"requestId": "req_a1b2c3...",
"status": "queued",
"pollUrl": "/v1/status/req_a1b2c3..."
}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.
{
"requestId": "req_a1b2c3...",
"status": "queued",
"pollUrl": "/v1/status/req_a1b2c3...",
"webhook": { "url": "https://example.com/hooks/pdfpipe", "secret": "whsec_..." },
"message": "Processing did not complete within 20s timeout. Result will be delivered via polling or webhook."
}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:
{
"requestId": "req_a1b2c3...",
"status": "processing",
"format": "json",
"type": "inline",
"createdAt": "2026-08-23T11:00:00.000Z",
"updatedAt": "2026-08-23T11:00:02.500Z"
}{
"requestId": "req_a1b2c3...",
"status": "complete",
"format": "json",
"type": "inline",
"detectedType": "inline",
"pagesProcessed": 48,
"creditsUsed": 1,
"resultUrl": "https://pdfpipe-results.s3...",
"expiresAt": "2026-08-24T11:00:00.000Z",
"processingDurationMs": 12450,
"createdAt": "2026-08-23T11:00:00.000Z",
"updatedAt": "2026-08-23T11:00:12.450Z"
}4. Handle failures
If the status is failed, the response includes an error object with a code, message, and suggestion. The codes are listed in the API Reference (for example NOT_A_PDF, PDF_NO_TEXT_LAYER, ATTACHMENT_DOWNLOAD_FAILED). A failed request still counts against your quota.
{
"requestId": "req_a1b2c3...",
"status": "failed",
"format": "json",
"type": "attachment",
"detectedType": "attachment",
"error": {
"code": "REDIRECT_TO_LOGIN",
"message": "The URL redirected to a login page instead of serving a PDF",
"suggestion": "The PDF may require authentication. Try using type: 'attachment' or ensure the URL is publicly accessible"
},
"createdAt": "2026-08-23T11:00:00.000Z",
"updatedAt": "2026-08-23T11: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
completeorfailed. - Download the result promptly. The
resultUrlis 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
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.status === "complete") {
return data;
}
if (!res.ok) {
// 4xx/5xx: { code, message, suggestion?, statusCode }
throw new Error(`${data.code}: ${data.message}`);
}
// 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");
}