# How to Extract Data From a PDF

Source: https://pdfparse.net/docs/how-to-extract-pdf-data

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.

```bash
npm install tus-js-client
```

## 2. Upload the PDF

Send `routingMode: "direct"` with `tableSlug`. Without it, PdfParse ignores the table slug and routes the document through automatic classification, and the job request in step 3 fails with `409 Conflict`.

Save this file as `extract-pdf.mjs`:

```js
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,
      tableSlug,
      routingMode: "direct",
    },
    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:

```js
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:

```js
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`.

```js
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:

```bash
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](https://pdfparse.net/docs/authentication).

