# PDF Extractor API: Text, Markdown, Tables, No Login (`conserving_celerytop/pdf-text-extractor`) Actor

Extract text from public PDF URLs: plain text, Markdown, tables, links, outline, page count and metadata per PDF or per page, with scanned pages flagged. $2 per 1,000 PDFs ($1.60 on Starter). Failed files are free. No login.

- **URL**: https://apify.com/conserving_celerytop/pdf-text-extractor.md
- **Developed by:** [Don Mangu](https://apify.com/conserving_celerytop) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 pdf extractions

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## PDF Text Extractor

PDF Text Extractor pulls the text out of public PDF files. Paste PDF URLs and get back, for each file, the plain text in reading order, clean Markdown with headings, lists and tables, every detected table as rows of cells, the web links inside the PDF, its bookmarks outline, the page count and the document metadata. Choose one row per PDF or one row per page. Pages with no text layer (scans) are flagged, so you know which files still need OCR.

It costs $2 per 1,000 PDFs, and PDFs that fail are free.

### What does PDF Text Extractor return?

- **Plain text** (`text`): the words of the PDF in reading order, with line breaks as in the file and a form feed between pages. Two-column pages are read column by column.
- **Markdown** (`markdown`): headings (`#`, `##`, `###`) from the font sizes, bullet and numbered lists, tables as Markdown tables, and words split by a hyphen at a line end joined back. Running headers, footers and page numbers are left out, so the Markdown is ready for an LLM, a RAG pipeline or a search index.
- **Tables** (`tables`): each table with `pageNumber`, `rowCount`, `columnCount` and `rows`, an array of cell texts per row. Easy to load into a spreadsheet.
- **Metadata**: `title`, `subject`, `keywords`, `creator`, `producer`, `creationDate`, `modificationDate`, `pdfVersion`, `language`, `isTagged`, `hasForm`, `pageCount` and `fileSizeBytes`.
- **Structure**: `links` (web links in the PDF), `outline` (bookmarks with their level) and `pages` (size, rotation, word count and table count per page).
- **Scan check**: `pdfType` is `text`, `scanned` or `mixed`, and `pagesWithoutText` lists the pages that are images only.
- **Status for every file**: `status` is `ok`, or says why nothing was extracted (`no_text_layer`, `password_protected`, `invalid_pdf`, `not_a_pdf`, `too_large`, `not_found`, `robots_disallowed`, `blocked`, `timeout` and others), with a plain-language `error`.

### How to extract text from a PDF, step by step

1. Open PDF Text Extractor and go to the **Input** tab.
2. In **PDF URLs**, paste one PDF address per line, for example `https://example.org/annual-report-2025.pdf`.
3. Pick **Row per**: **Document** gives one row with the whole text of each PDF; **Page** gives one row per page, which helps when an answer must cite a page or when you split text into chunks.
4. Keep **Plain text**, **Markdown** and **Tables** on, or turn off what you do not need.
5. Optional: turn on **Save .txt and .md files** to get a download link for each PDF's text.
6. Click **Start**. The example PDF takes a few seconds.
7. Open the **Output** tab. **PDFs** shows the status, pages, words and text of each file, **Pages** shows the page rows, and **Metadata** shows the document details. Download as JSON, CSV or Excel, or read the results through the Apify API.

### How much does PDF text extraction cost?

You pay per PDF, and only for PDFs that gave text:

- `pdf-extracted`: **$0.002 per PDF** ($2 per 1,000 PDFs). This covers a PDF of up to 100 pages.
- `extra-100-pages`: $0.002 for each further 100 pages (or part of 100) of a long PDF.

Files with no text are **not charged**: scanned-only PDFs, password-protected files, broken links, pages that are not PDFs, files over the size limit and addresses that robots.txt does not allow all return a row with the reason and cost nothing.

**Worked example:** 1,000 PDFs of 20 pages, plus one 250-page report, cost 1,001 x $0.002 + 2 x $0.002 = **$2.006**. If 50 of the 1,000 are scans with no text layer, those 50 are free and the run costs $1.906.

Set a maximum cost on the run and PDF Text Extractor stops starting new PDFs when the next one would go over it.

### Input example

```json
{
    "pdfUrls": [
        "https://raw.githubusercontent.com/py-pdf/sample-files/main/026-latex-multicolumn/multicolumn.pdf"
    ],
    "rowPer": "document",
    "includeText": true,
    "includeMarkdown": true,
    "extractTables": true
}
```

### Output example

```json
{
    "rowType": "document",
    "url": "https://raw.githubusercontent.com/py-pdf/sample-files/main/026-latex-multicolumn/multicolumn.pdf",
    "fileName": "multicolumn.pdf",
    "status": "ok",
    "producer": "pdfTeX-1.40.21",
    "creationDate": "2024-01-03T08:38:26.000Z",
    "pageCount": 3,
    "pagesExtracted": 3,
    "pdfType": "text",
    "wordCount": 1075,
    "tableCount": 1,
    "text": "Two-Column Document with Lorem Ipsum\n\n...",
    "markdown": "# Two-Column Document with Lorem Ipsum\n\n...",
    "tables": [
        {
            "pageNumber": 3,
            "rowCount": 6,
            "columnCount": 5,
            "rows": [
                ["Country", "Population (millions)", "Area (km2)", "Capital", "Official Language"],
                ["Austria", "8.9", "83,879", "Vienna", "German"]
            ]
        }
    ],
    "charged": true
}
```

### Use cases for a PDF to text converter

- Feed reports, papers, manuals and filings to an LLM or a vector database as Markdown.
- Pull tables from statistical releases, price lists and annual reports into a spreadsheet.
- Index a document library for search, with page numbers for citations.
- Sort a pile of PDFs into text PDFs and scans before paying for OCR.
- Read PDF metadata (dates, producer, language, page count) in bulk.

### Bulk lists

Use a long list of PDF addresses from an Apify Dataset or from a file. Pick the Dataset under **Dataset with the list** and write the name of its field under **Field in the dataset**. Leave the field name empty to use the first text field. Or paste the web address of a .txt, .csv or .json file under **List file URL**. A .csv file uses its first column. A header row is skipped. The file can be up to 10 MB. Entries from the Dataset and the file are added to the ones typed under **PDF URLs**, and duplicates are removed. The limit under **Maximum PDFs** still applies. The price per PDF does not change.

### Related Actors

- **Document to Markdown Converter** turns Word, PowerPoint, Excel, EPUB and other formats into Markdown, and finds document links on a web page.
- For scanned PDFs, use an OCR Actor on the pages listed in `pagesWithoutText`.

### FAQ

**Is it legal to extract text from PDFs?** PDF Text Extractor reads only files at public addresses you give it, with no login. It checks each site's robots.txt, identifies itself honestly with a contact address, and stops contacting a site that answers 401, 403 or 429. It does not return author names from the metadata. You are responsible for having the right to process the documents you submit.

**Does it do OCR?** No. It reads the text layer that the PDF already has. Pages that are only images are listed in `pagesWithoutText`, and a PDF with no text at all returns `no_text_layer` and is free.

**How big can a PDF be?** Up to 200 MB and 5,000 pages per file. The defaults are 50 MB and 500 pages; a PDF with more pages returns the first pages and `truncated: true`. Give the run more memory (2 GB or more) for files over 50 MB. Very long text (over 1,000,000 characters) is saved as a file and linked in `textFileUrl` and `markdownFileUrl`.

**Why is a table missing or split?** Tables are found from the position of the text. Tables drawn as images, or with merged cells across many rows, may come back as plain lines. The text is still in `text` and `markdown`.

**Something went wrong?** Look at `status` and `error` in the row. If a PDF opens in your browser but fails here, open an issue on the Issues tab with the URL.

# Actor input Schema

## `pdfUrls` (type: `array`):

Add the public web addresses of PDF files, one per line. Each PDF becomes one dataset row (or one row per page, see Row per).

## `listDatasetId` (type: `string`):

Pick an Apify Dataset that holds your PDF addresses, one entry per item. They are added to the entries typed above.

## `listDatasetField` (type: `string`):

Name of the dataset field that holds the entry. Leave empty to use the first text field it finds.

## `listFileUrl` (type: `string`):

Web address of a .txt, .csv or .json file with your PDF addresses, one entry per line. A header row is skipped. Up to 10 MB.

## `rowPer` (type: `string`):

Choose one row per PDF (full text, tables, links and metadata together) or one row per page (text and tables of each page, for page citations and chunking).

## `includeText` (type: `boolean`):

Return the plain text in reading order, with line breaks as in the PDF and a form feed between pages.

## `includeMarkdown` (type: `boolean`):

Return Markdown with headings, lists and tables, with running headers, footers and page numbers removed. Ready for LLMs and RAG pipelines.

## `extractTables` (type: `boolean`):

Detect tables and return each one as rows of cells, in addition to the Markdown table.

## `saveFiles` (type: `boolean`):

Also save each PDF's text and Markdown as files in the key-value store, linked in textFileUrl and markdownFileUrl (with Row per: Document). Very long documents are always saved this way.

## `maxPdfs` (type: `integer`):

Read at most this many PDFs from the list.

## `maxPagesPerPdf` (type: `integer`):

Read at most this many pages of each PDF, starting from page 1. The row says truncated: true when a PDF has more.

## `maxFileSizeMegabytes` (type: `integer`):

Download PDFs up to this size. Larger files return status too_large and are free. For files over 50 MB, give the run 2 GB of memory or more.

## `maxConcurrency` (type: `integer`):

Read up to this many PDFs at the same time (at most 2 per website). Lowered automatically when large files would not fit in the run's memory.

## `downloadTimeoutSecs` (type: `integer`):

Wait this long for one PDF download before retrying (3 attempts).

## Actor input object example

```json
{
  "pdfUrls": [
    "https://raw.githubusercontent.com/py-pdf/sample-files/main/026-latex-multicolumn/multicolumn.pdf"
  ],
  "rowPer": "document",
  "includeText": true,
  "includeMarkdown": true,
  "extractTables": true,
  "saveFiles": false,
  "maxPdfs": 1000,
  "maxPagesPerPdf": 500,
  "maxFileSizeMegabytes": 50,
  "maxConcurrency": 3,
  "downloadTimeoutSecs": 90
}
```

# Actor output Schema

## `overview` (type: `string`):

url, status, title, pageCount, pdfType, wordCount, tableCount, text, markdown, error and charged.

## `pages` (type: `string`):

One row per page when Row per is Page: pageNumber, hasText, text, markdown.

## `metadata` (type: `string`):

title, subject, keywords, creator, producer, dates, pdfVersion, links and outline.

## `files` (type: `string`):

The .txt and .md files (with Save .txt and .md files on, or for very long documents), STATS and STATE.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "pdfUrls": [
        "https://raw.githubusercontent.com/py-pdf/sample-files/main/026-latex-multicolumn/multicolumn.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("conserving_celerytop/pdf-text-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "pdfUrls": ["https://raw.githubusercontent.com/py-pdf/sample-files/main/026-latex-multicolumn/multicolumn.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("conserving_celerytop/pdf-text-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pdfUrls": [
    "https://raw.githubusercontent.com/py-pdf/sample-files/main/026-latex-multicolumn/multicolumn.pdf"
  ]
}' |
apify call conserving_celerytop/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,conserving_celerytop/pdf-text-extractor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yqxMvvM3Dum4HVoCh/builds/kw6gXQggrUUi6Vdgt/openapi.json
