Docs

API Reference

Complete reference for every PDFPipe endpoint, including request parameters, response schemas, authentication, and error handling.

Base URL

All API requests are made to:

https://api.pdfpipe.dev/v1

HTTPS is required for all requests. HTTP connections are rejected.

Authentication

Authenticate by including your API key in the Authorization header:

Authorization: Bearer pk_...

API keys are created in the Dashboard. Each key can optionally be restricted to specific IP addresses. Keys start with pk_ and are shown only once at creation. An X-API-Key header is accepted as an alternative to Authorization: Bearer.

Convert a PDF

POST/v1/convert

Fetch a PDF from a public URL and return it in your chosen output format. Inline PDFs under 10 MB are processed synchronously (200). Attachment PDFs (headless browser), larger files, requests with async: true, and jobs that exceed your timeout window return a poll URL (202). Use returnMethod: "inline" to embed the converted payload in JSON when the response is synchronous (or on status when using ?returnMethod=inline).

What the extractor does: it reads the text layer of digital-born PDFs page by page (using pdf.js via pdf-parse) and the PDF's Info dictionary. It does not OCR scanned pages, detect tables, or return coordinates or fonts. It does not send cookies or credentials to the source URL, so login-walled PDFs fail with URL_UNAUTHORIZED, URL_FORBIDDEN, or REDIRECT_TO_LOGIN. Token-in-URL links and redirect chains work. Hosts behind bot challenges (Cloudflare, Akamai) often do not.

Request body

ParameterTypeDescription
urlrequiredstringThe HTTP or HTTPS URL of the PDF. Max 2048 characters.
formatstringOutput format. One of: json, text, markdown, xml, csv, base64, binary, png, jpg, webp. Default: json. All 10 formats are available on every tier.
pagesstringOptional page selection: "1", "1-5", "1,3,5", or "last" (combinable, 1-based, max 100 selected pages). Applies to json, text, markdown, xml, and csv. Image formats render the first selected page. Default: all pages.
typestringForce the fetch path. "inline" (plain HTTP fetch) or "attachment" (headless Chromium). Omit to auto-detect from the response headers (all tiers).
asyncbooleanForce asynchronous processing. Default: false.
returnMethodstringHow the result is delivered when complete: "file" (presigned resultUrl) or "inline" (content + contentType in the JSON body, up to ~5.5 MB). Default: file.
timeoutintegerOptional. Max seconds (1-60) to attempt synchronous processing before the job is queued. Omit for default behaviour.
webhookobjectOptional. After async completion, POSTs the result to an HTTPS URL. Shape: { url: string, secret?: string }. If secret is omitted, one is generated and returned on the async response. Payloads are signed with HMAC-SHA256 (see Webhooks).

Example request

Request body
{
  "url": "https://example.com/report.pdf",
  "format": "json",
  "pages": "1-5",
  "type": "inline",
  "async": false,
  "returnMethod": "file",
  "timeout": 30,
  "webhook": { "url": "https://example.com/hooks/pdfpipe" }
}

Responses

With returnMethod: "inline", synchronous 200 responses include content (always a string; parse it for the json format), contentType, and for binary formats (png, jpg, webp, binary) contentEncoding: "base64". If inline delivery exceeds the size limit, the API returns a presigned URL instead and sets returnMethodFallback: true with returnMethodFallbackReason.

200 - Synchronous (returnMethod: file, default)
{
  "requestId": "req_a1b2c3...",
  "status": "complete",
  "format": "json",
  "pagesProcessed": 3,
  "creditsUsed": 1,
  "resultUrl": "https://pdfpipe-results.s3...",
  "expiresAt": "2026-08-24T12:00:00.000Z"
}
200 - Synchronous (returnMethod: inline)
{
  "requestId": "req_a1b2c3...",
  "status": "complete",
  "format": "json",
  "pagesProcessed": 3,
  "creditsUsed": 1,
  "contentType": "application/json",
  "content": "{\"pages\":[{\"pageNumber\":1,\"text\":\"...\"}],\"metadata\":{...},\"totalPages\":3,\"extractedAt\":\"...\"}",
  "processingDurationMs": 1840
}
200 - Synchronous, inline requested but too large (fallback to file)
{
  "requestId": "req_a1b2c3...",
  "status": "complete",
  "format": "png",
  "pagesProcessed": 40,
  "creditsUsed": 1,
  "resultUrl": "https://pdfpipe-results.s3...",
  "expiresAt": "2026-08-24T12:00:00.000Z",
  "returnMethodFallback": true,
  "returnMethodFallbackReason": "Result size (7.2MB) exceeds inline limit (5.5MB)"
}
202 - Asynchronous (queued)
{
  "requestId": "req_a1b2c3...",
  "status": "queued",
  "pollUrl": "/v1/status/req_a1b2c3..."
}
202 - With webhook / timeout-to-async message (fields optional)
{
  "requestId": "req_a1b2c3...",
  "status": "queued",
  "pollUrl": "/v1/status/req_a1b2c3...",
  "webhook": {
    "url": "https://example.com/hooks/pdfpipe",
    "secret": "whsec_generated_or_provided"
  },
  "message": "Processing did not complete within 30s timeout. Result will be delivered via polling or webhook."
}
4xx - Bad source URL (see Error Codes below)
{
  "code": "NOT_A_PDF",
  "message": "The URL did not return a PDF file",
  "suggestion": "Verify the URL points directly to a PDF file",
  "statusCode": 422
}

Webhooks

When you include webhook on a convert request that ends up asynchronous, PDFPipe POSTs to your HTTPS URL when processing finishes. Webhooks are available on every tier. The async 202 response includes webhook.url and webhook.secret (generated if you did not supply secret; supplied secrets must be at least 16 characters). Verify deliveries with HMAC-SHA256 over the raw request body using that secret; the request includes X-PDFPipe-Signature: sha256=<hex> (plus X-PDFPipe-Request-Id and X-PDFPipe-Event). The payload carries event (conversion.complete or conversion.failed), requestId, status, format, pagesProcessed, creditsUsed, inline content when the job used returnMethod: "inline" and fits, an error object on failure, and a timestamp. Delivery is retried 3 times (2s, 8s, 32s) on non-2xx responses, with a 10-second timeout per attempt.

Batch Convert

POST/v1/convert/batch

Submit multiple PDF URLs for conversion in a single request. All jobs are processed asynchronously and each URL counts as one request against your quota. The maximum batch size depends on your tier: 5 (Free), 25 (Starter), 50 (Pro), 100 (Business). Larger batches are rejected with VALIDATION_ERROR.

Request body

ParameterTypeDescription
urlsrequiredarrayArray of URL objects. Each must have a url field. Optional per-item: format, type, pages, and metadata (string key/value pairs stored with the request).
defaultsobjectDefaults applied to every URL that does not set its own: format and type.
returnMethodstringBatch-wide: "file" (default) or "inline". Applies to all URLs.
webhookobjectBatch-wide { url, secret? }. One delivery per URL as each finishes.

Example

Request body
{
  "urls": [
    { "url": "https://example.com/invoice-1.pdf" },
    { "url": "https://example.com/invoice-2.pdf", "format": "text" },
    {
      "url": "https://example.com/invoice-3.pdf",
      "type": "attachment",
      "pages": "1-3",
      "metadata": { "department": "finance" }
    }
  ],
  "defaults": { "format": "json" },
  "returnMethod": "inline",
  "webhook": { "url": "https://example.com/hooks/batch" }
}
202 - Response
{
  "batchId": "batch_a1b2c3d4e5f6...",
  "requests": [
    { "requestId": "req_...", "url": "https://example.com/invoice-1.pdf", "status": "queued" },
    { "requestId": "req_...", "url": "https://example.com/invoice-2.pdf", "status": "queued" },
    { "requestId": "req_...", "url": "https://example.com/invoice-3.pdf", "status": "queued" }
  ],
  "pollUrl": "/v1/batch/batch_a1b2c3d4e5f6..."
}

Poll Request Status

GET/v1/status/:requestId

Poll the status of an asynchronous conversion request. Returns the current status and, when complete, a presigned result URL or inline content depending on query parameters.

Query parameters

ParameterTypeDescription
returnMethodstringWhen complete, return embedded content and contentType (same semantics as convert). Use inline. Default behaviour matches file.

Response

Status values: pending, processing, complete, failed. Once your plan's retention window has passed, a complete request returns resultUrl: null and expired: true. A failed request includes an error object with code, message, and suggestion.

200 - Complete (default / file delivery)
{
  "requestId": "req_a1b2c3...",
  "status": "complete",
  "format": "json",
  "type": "inline",
  "detectedType": "inline",
  "pagesProcessed": 3,
  "creditsUsed": 1,
  "resultUrl": "https://pdfpipe-results.s3...",
  "expiresAt": "2026-08-24T12:00:00.000Z",
  "processingDurationMs": 4230,
  "createdAt": "2026-08-23T11:00:00.000Z",
  "updatedAt": "2026-08-23T11:00:04.230Z"
}
200 - Complete (?returnMethod=inline)
{
  "requestId": "req_a1b2c3...",
  "status": "complete",
  "format": "json",
  "type": "inline",
  "detectedType": "inline",
  "pagesProcessed": 3,
  "creditsUsed": 1,
  "contentType": "application/json",
  "content": "{\"pages\":[{\"pageNumber\":1,\"text\":\"...\"}],...}",
  "processingDurationMs": 4230,
  "createdAt": "2026-08-23T11:00:00.000Z",
  "updatedAt": "2026-08-23T11:00:04.230Z"
}
200 - Failed
{
  "requestId": "req_a1b2c3...",
  "status": "failed",
  "format": "json",
  "type": "attachment",
  "error": {
    "code": "ATTACHMENT_DOWNLOAD_FAILED",
    "message": "The headless browser could not download the PDF from this URL",
    "suggestion": "The page may require JavaScript interaction or authentication"
  },
  "createdAt": "2026-08-23T11:00:00.000Z",
  "updatedAt": "2026-08-23T11:00:08.000Z"
}

Batch Status

GET/v1/batch/:batchId

Check the overall status of a batch job and get individual request results.

200 - Response
{
  "batchId": "batch_a1b2c3d4e5f6...",
  "total": 3,
  "completed": 2,
  "failed": 0,
  "pending": 1,
  "requests": [
    { "requestId": "req_...", "status": "complete", "resultUrl": "https://..." },
    { "requestId": "req_...", "status": "complete", "resultUrl": "https://..." },
    { "requestId": "req_...", "status": "processing" }
  ]
}

Usage

GET/v1/usage

Returns your current month's usage statistics.

200 - Response
{
  "tier": "starter",
  "month": "2026-08",
  "requestsUsed": 47,
  "requestsLimit": 500,
  "remaining": 453,
  "testRequestsUsed": 2,
  "testRequestsLimit": 10,
  "testRequestsRemaining": 8,
  "resetsAt": "2026-09-01T00:00:00.000Z"
}

API Keys

GET/v1/keys

List all API keys for your account.

200 - Response
{
  "keys": [
    {
      "keyId": "a1b2c3d4e5f6",
      "name": "Production key",
      "prefix": "pk_a1b2c",
      "allowedIps": ["203.0.113.10"],
      "createdAt": "2026-01-15T10:00:00.000Z",
      "revokedAt": null
    }
  ]
}
POST/v1/keys

Create a new API key. The full key value is returned only in this response.

Request body
{
  "name": "Production key",
  "allowedIps": ["203.0.113.10"]
}
201 - Response
{
  "keyId": "a1b2c3d4e5f6",
  "name": "Production key",
  "prefix": "pk_a1b2c",
  "key": "pk_a1b2c3d4...full_key_here"
}
PATCH/v1/keys/:keyId

Update the IP allowlist on an existing API key. Pass an array of IP addresses to restrict the key, or an empty array / omit the field to allow all IPs. Cannot be used on revoked keys.

Request body
{
  "allowedIps": ["203.0.113.10", "198.51.100.0"]
}
200 - Response
{
  "keyId": "a1b2c3d4e5f6",
  "name": "Production key",
  "prefix": "pk_a1b2c",
  "allowedIps": ["203.0.113.10", "198.51.100.0"],
  "createdAt": "2026-01-15T10:00:00.000Z"
}
DELETE/v1/keys/:keyId

Revoke an API key. Returns 204 No Content on success.

Error Codes

Errors return a JSON body with a code, a human-readable message, and the statusCode. Errors about the source PDF or URL also carry a suggestion.

Error response format
{
  "code": "VALIDATION_ERROR",
  "message": "url: URL must use http or https protocol",
  "statusCode": 400
}

Request and account errors

CodeHTTPDescription
UNAUTHORIZED401Invalid or missing API key.
VALIDATION_ERROR400Request body failed validation (bad URL, unknown format, invalid pages string, batch too large). Check the message for specifics.
NOT_FOUND404The requested request, batch, or key does not exist.
FORBIDDEN403Insufficient permissions for this action.
ACCOUNT_SUSPENDED403Account is suspended. Contact support.
FILE_TOO_LARGE413PDF exceeds your tier's file size limit.
RATE_LIMIT_EXCEEDED429Monthly request limit exceeded (Free, Starter, Pro). Business bills overage instead.
SSRF_BLOCKED403URL points to a blocked private or reserved address.
ATTACHMENT_LIMIT_REACHED403Monthly attachment PDF cap reached (Free tier: 25/month).
FORMAT_NOT_ALLOWED403Output format not available on your tier (all current tiers include all 10 formats).
PAYLOAD_TOO_LARGE413Request body larger than 64 KB.
PROCESSING_ERROR500Unclassified processing failure. Retry or contact support.
INTERNAL_ERROR500Unexpected server error.
EMAIL_NOT_VERIFIED403Verify your email address before using the API.
API_KEY_LIMIT_REACHED409Maximum number of API keys reached for this user.
MEMBER_LIMIT_REACHED409Account team member limit reached.
CHAT_NOT_AVAILABLE403Dashboard AI Help is available on Starter, Pro, and Business only.
CHAT_RATE_LIMIT_EXCEEDED429Dashboard AI Help daily limit reached (50 questions per user per day).

Source URL and PDF errors

Returned synchronously with the HTTP status shown, or inside the error object of a failed status/batch entry when the job ran asynchronously. Each includes a suggestion.

CodeHTTPDescription
NOT_A_PDF422The URL did not return a PDF file (for example an HTML page). Verify the URL points directly to a PDF.
REDIRECT_TO_LOGIN422The URL redirected to a login page instead of serving a PDF. Login-walled PDFs are not supported.
PDF_PASSWORD_PROTECTED422The PDF is password-protected and cannot be extracted.
PDF_CORRUPTED422The PDF appears to be corrupted or invalid.
PDF_NO_TEXT_LAYER422The PDF contains scanned images only. Text extraction is not available for image-only PDFs (no OCR).
PDF_EMPTY422The PDF has no pages or content.
URL_NOT_FOUND404The source URL returned 404.
URL_UNAUTHORIZED401The source URL returned 401. PDFPipe does not send credentials to the source.
URL_FORBIDDEN403The source URL returned 403. The host may require authentication or block automated clients.
URL_UNREACHABLE502Could not connect to the source host (DNS failure, connection refused, or reset).
URL_SERVER_ERROR502The source host returned a 5xx error.
URL_HTTP_ERROR502The source host returned another non-success HTTP status.
SSL_CERTIFICATE_ERROR502The source host's TLS certificate is invalid or expired.
URL_TIMEOUT504The source host took too long to respond.
PROCESSING_TIMEOUT504Processing took longer than the maximum allowed time. Try a smaller file or a page range.
ATTACHMENT_DOWNLOAD_FAILED502The headless browser could not download the PDF. The page may need JavaScript interaction or a login.

Rate Limits

Quotas are applied per account on a monthly cycle that resets on the 1st. When you exceed your limit on Free, Starter, or Pro, requests return 429 RATE_LIMIT_EXCEEDED. Business accounts are not capped; requests beyond 20,000 are billed at the overage rate. The Free tier also allows at most 25 attachment (headless browser) PDFs per month. Webhooks, page ranges, batch, and auto-detection are available on all tiers.

Dashboard AI Help is separate from PDF conversion quotas: paid plans (Starter+) include an in-dashboard assistant (Claude) at /dashboard/chat with documentation-grounded answers. Free tier does not include AI Help. Limit: 50 questions per user per calendar day (UTC). See also Getting Started.

TierRequests/moMax FileFormatsBatch SizeAPI KeysURL ExpiryAI Help
Free3005 MBAll 10521 hourNo
Starter1,00010 MBAll 1025524 hoursYes (50/day)
Pro5,00025 MBAll 1050107 daysYes (50/day)
Business20,00050 MBAll 101002530 daysYes (50/day)