PdfParse Docs

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-client

2. 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.pdf

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