How to Extract Data From a PDF
Upload a PDF, create an extraction job, wait for completion, and query the structured rows.
Use this procedure after you create a table.
You need:
- a project API key in
PDFPARSE_API_KEY - the table slug from the create-table response
- a synthetic or redacted PDF
- Node.js 20 or later
1. Install the upload client
PdfParse uses the TUS resumable upload protocol.
npm install tus-js-client2. Upload the PDF
Save this file as extract-pdf.mjs:
import fs from "node:fs";
import path from "node:path";
import { Upload } from "tus-js-client";
const apiKey = process.env.PDFPARSE_API_KEY;
const tableSlug = process.env.PDFPARSE_TABLE_SLUG;
const filePath = process.argv[2];
if (!apiKey || !tableSlug || !filePath) {
throw new Error(
"Set PDFPARSE_API_KEY and PDFPARSE_TABLE_SLUG, then provide a PDF path.",
);
}
const file = fs.createReadStream(filePath);
const size = fs.statSync(filePath).size;
const filename = path.basename(filePath);
const uploadResult = await new Promise((resolve, reject) => {
const upload = new Upload(file, {
endpoint: "https://api.pdfparse.net/v1/uploads",
uploadSize: size,
headers: {
Authorization: `Bearer ${apiKey}`,
},
metadata: {
filename,
name: filename,
type: "application/pdf",
file_size: String(size),
tableSlug,
},
retryDelays: [0, 1000, 3000, 5000],
removeFingerprintOnSuccess: true,
onError: reject,
onSuccess() {
const encodedUploadId = upload.url?.split("?")[0]?.split("/").filter(Boolean).at(-1);
if (!encodedUploadId) {
reject(new Error("The upload did not return an upload ID."));
return;
}
const uploadId = decodeURIComponent(encodedUploadId);
resolve({
uploadId,
documentKey: uploadId,
});
},
});
upload.start();
});3. Create the extraction job
Add this code after the upload code:
const baseUrl = "https://api.pdfparse.net/v1";
const headers = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
const jobResponse = await fetch(`${baseUrl}/jobs`, {
method: "POST",
headers: {
...headers,
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
tableSlug,
documentKeys: [uploadResult.documentKey],
}),
});
if (!jobResponse.ok) {
throw new Error(await jobResponse.text());
}
const job = await jobResponse.json();
console.log(`Created job ${job.id}`);The job runs asynchronously.
4. Wait for completion
Add a bounded polling loop for a basic integration:
const wait = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
let completedJob;
for (let attempt = 0; attempt < 60; attempt += 1) {
const response = await fetch(`${baseUrl}/jobs/${job.id}`, { headers });
if (!response.ok) {
throw new Error(await response.text());
}
const currentJob = await response.json();
if (currentJob.status === "completed") {
completedJob = currentJob;
break;
}
if (currentJob.status === "failed") {
throw new Error(`Job ${job.id} failed.`);
}
await wait(2000);
}
if (!completedJob) {
throw new Error(`Job ${job.id} did not finish in time.`);
}Use a webhook instead of polling for a production integration.
5. Query the extracted rows
The completed job response contains table_id.
const rowsResponse = await fetch(`${baseUrl}/rows/search`, {
method: "POST",
headers,
body: JSON.stringify({
table_id: completedJob.table_id,
filters: [],
page: 1,
pageSize: 50,
}),
});
if (!rowsResponse.ok) {
throw new Error(await rowsResponse.text());
}
const rows = await rowsResponse.json();
console.log(JSON.stringify(rows, null, 2));Run the complete script:
export PDFPARSE_TABLE_SLUG="invoices-replace_with_your_slug"
node extract-pdf.mjs ./sample-invoice.pdf6. Validate the result
Check the extracted values against the source PDF.
Confirm that:
- required fields are present
- codes keep leading zeros
- dates use the expected format
- amounts use the expected decimal value
- repeating rows have the expected count
Do not send unvalidated OCR data directly to a financial or operational system.
For production event delivery, use the webhook endpoints in the API reference. For authentication errors, read Authentication.