Busabase
Busabase is an open-source (MIT) database and workspace for AI agents — data, docs, skills, and apps in one place. Run it locally, self-host it inside your network, or use Busabase Cloud, and turn age
Extract text, page ranges, RAG chunks, metadata and links from PDF URLs (incl. Drive/Dropbox links) as JSON. $0.004 per PDF, no per-page fee, failures f...
DataTypeScript · DockerfileGitHub @josh99smithOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Extract text, per-page text, metadata and links from any PDF URL as JSON or Markdown, ready for RAG and LLM ingestion. Works with Google Drive and Dropbox links. One flat price per PDF, no per-page fees, failed downloads free. Runs on Apify Store: https://apify.com/josh99smith/pdf-text-extractor
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
PDF text extractor and PDF to text API: give it public PDF URLs and it downloads each file and returns the full text, the text of every page, ready-made chunks for RAG, document metadata (title, author, dates, producer) and every link in the document as JSON you can search, index or feed to an LLM.
It is built for RAG and LLM ingestion pipelines, research and archiving: no servers, no PDF library to maintain, one flat price per PDF, and files that cannot be processed are reported free of charge.Read the full README ↗PDF text extractor and PDF to text API: give it public PDF URLs and it downloads each file and returns the full text, the text of every page, ready-made chunks for RAG, document metadata (title, author, dates, producer) and every link in the document as JSON you can search, index or feed to an LLM.
It is built for RAG and LLM ingestion pipelines, research and archiving: no servers, no PDF library to maintain, one flat price per PDF, and files that cannot be processed are reported free of charge.
pageRange such as 1-5, 8, 12-chunks[] with overlap for embeddings, split on paragraph boundariesFor each URL the Actor rewrites known cloud share links to direct-download URLs, downloads the file over plain HTTP (following redirects), enforces your size limit from Content-Length and again while streaming, and checks the %PDF signature so that HTML login and error pages are reported instead of parsed. The PDF is opened with Mozilla's pdf.js: text is read page by page, lines are reassembled into paragraphs, hyphenation is repaired, metadata comes from the document info and XMP, and links from annotations and text. Only the text layer is extracted; scanned image-only PDFs return status: "no-text-layer" (no OCR in this version).
{
"urls": [
"https://arxiv.org/pdf/1706.03762",
"https://drive.google.com/file/d/1A2b3C4d5E6f7G8h9I0jKLMNOPqrstuv/view?usp=sharing",
"https://www.dropbox.com/scl/fi/abcd1234/report.pdf?rlkey=k3y&dl=0"
],
"outputFormat": "markdown",
"pageRange": "1-5, 8, 12-",
"chunkSize": 1500,
"chunkOverlap": 200,
"password": ""
}
pageRange selects pages (1-based): 1-5, 8, 12- means pages 1 to 5, page 8 and page 12 to the end. Numbers past the end of a document are ignored, duplicates are merged, and Max pages per PDF still caps the total. pageCount always reports the real length; pages[].page tells you what was extracted. A range that selects nothing (50- on a 9-page file) gives a free record with status: "no-pages-in-range". Invalid syntax stops the run with a clear message before any download.
Set PDF password (a secret input) and it is tried on every file in the run; files that open without a password are unaffected. A wrong password gives a free errorType: "encrypted" record saying the password was rejected.
These formats are rewritten before downloading and the record then carries resolvedUrl:
drive.google.com/file/d/<id>/view, open?id=<id>, uc?id=<id>www.dropbox.com/s/... and /scl/fi/...?rlkey=...&dl=0 (dl=1 is set)1drv.ms/..., onedrive.live.com/redir?...; SharePoint file shares get download=1github.com/<owner>/<repo>/blob/... becomes the raw fileThe file must be shared with "anyone with the link"; private or expired shares are reported as not-a-pdf, free of charge.
With chunkSize above 0 (characters; 800-2000 is typical) every record gets chunks[] of { index, page, text, charCount }. The splitter cuts at paragraph breaks where possible, then line breaks, sentence ends and spaces, and repeats the last chunkOverlap characters (default 200, at most half the chunk size) at the start of the next chunk so context survives the cut. page is where the chunk starts, so you can cite it. At most 2,000 chunks per PDF.
One record per PDF (text trimmed):
{
"url": "https://bitcoin.org/bitcoin.pdf",
"finalUrl": "https://bitcoin.org/bitcoin.pdf",
"success": true,
"status": "ok",
"fileName": "bitcoin.pdf",
"fileSizeBytes": 184292,
"pageCount": 9,
"pagesExtracted": 3,
"wordCount": 1263,
"charCount": 7805,
"hasText": true,
"pagesWithText": 3,
"metadata": {
"title": null,
"author": null,
"creator": "Writer",
"producer": "OpenOffice.org 2.4",
"creationDate": "2009-03-24T17:33:15.000Z",
"modDate": null,
"pdfVersion": "1.4",
"encrypted": false
},
"links": ["www.bitcoin.org"],
"text": "Bitcoin: A Peer-to-Peer Electronic Cash System\n\nSatoshi Nakamoto ...",
"textTruncated": false,
"pages": [
{ "page": 1, "text": "Bitcoin: A Peer-to-Peer Electronic Cash System ...", "charCount": 3049 },
{ "page": 2, "text": "2. Transactions\n\nWe define an electronic coin ...", "charCount": 2443 },
{ "page": 5, "text": "8. Simplified Payment Verification ...", "charCount": 2309 }
],
"chunks": [
{ "index": 0, "page": 1, "text": "Bitcoin: A Peer-to-Peer Electronic Cash System ...", "charCount": 1246 },
{ "index": 1, "page": 1, "text": "are broadcast on a best effort basis ...", "charCount": 1295 }
],
"fetchedAt": "2026-09-18T21:30:10.258Z"
}
Rewritten share links add "resolvedUrl": "https://drive.google.com/uc?export=download&id=...". Failed URLs are still recorded:
{
"url": "https://example.com/login",
"success": false,
"errorType": "not-a-pdf",
"error": "Response is an HTML page (often a login, consent or error page), not a PDF",
"statusCode": 200,
"fetchedAt": "..."
}
| Field | Description |
|---|---|
url, resolvedUrl, finalUrl | The URL you supplied, the direct-download URL (only for rewritten share links) and the URL after redirects. |
success, status | success is true when the PDF was parsed; status is ok (billed), no-text-layer or no-pages-in-range (both free). |
fileName, fileSizeBytes | Name from Content-Disposition or the URL, and the downloaded size. |
pageCount, pagesExtracted | Real number of pages and how many were extracted (after pageRange and Max pages per PDF). |
wordCount, charCount, hasText | Size of the extracted plain text; hasText is false for scanned, image-only PDFs. |
metadata | title, author, subject, keywords, creator, producer, creationDate, modDate (ISO 8601), pdfVersion, encrypted. |
text, markdown | Full text (paragraphs separated by blank lines) or Markdown with headings and --- page breaks. Each capped at 2 MB. |
pages[] | page, text, charCount per extracted page (when Include per-page text is on). |
chunks[] | index, page, text, charCount per chunk (when Chunk size is above 0). |
links[] | Unique URLs from link annotations and from the text. |
errorType | For failures: invalid-url, not-a-pdf, too-large, encrypted, http-error, , , or . |
Run the Actor with chunkSize set and embed chunks[] directly, no local PDF parsing or text splitter needed:
from apify_client import ApifyClient
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("josh99smith/pdf-text-extractor").call(
run_input={"urls": ["https://arxiv.org/pdf/1706.03762"], "chunkSize": 1500, "chunkOverlap": 200, "perPage": False}
)
docs = [
Document(page_content=c["text"], metadata={"source": item["url"], "page": c["page"], "chunk": c["index"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items()
if item.get("status") == "ok"
for c in item["chunks"]
]
store = FAISS.from_documents(docs, OpenAIEmbeddings())
print(store.similarity_search("What is multi-head attention?", k=3))
For LlamaIndex, build TextNode objects the same way and pass them to VectorStoreIndex(nodes).
curl -X POST "https://api.apify.com/v2/acts/josh99smith~pdf-text-extractor/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://bitcoin.org/bitcoin.pdf"], "outputFormat": "markdown", "pageRange": "1-3"}'
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('josh99smith/pdf-text-extractor').call({
urls: ['https://bitcoin.org/bitcoin.pdf'],
chunkSize: 1500,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].chunks.length, 'chunks');
The Actor is exposed as a tool by the Apify MCP server, so an AI agent can call it by name. Add this to your MCP client configuration (Claude Desktop, Claude Code, Cursor, VS Code, Windsurf and others):
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=josh99smith/pdf-text-extractor",
"headers": { "Authorization": "Bearer <YOUR_API_TOKEN>" }
}
}
}
Then ask, for example: "Extract the text of https://arxiv.org/pdf/1706.03762 with josh99smith/pdf-text-extractor and summarise section 3." The agent fills in the input, runs the Actor and reads the dataset back; you pay the same per-result price as in the Console.
The Actor can also be scheduled, or connected to Zapier, Make, n8n and Google Sheets in the Integrations tab.
You pay a flat price per successfully processed PDF (shown next to the Start button). There is no start fee and no per-page fee: a 400-page manual and a one-page flyer cost the same, and page ranges, passwords, share-link resolution and chunking are included rather than billed as extra events. Scanned PDFs without a text layer, invalid URLs, non-PDF responses, oversized, password-rejected and unreachable files cost nothing. The Actor stops at the maximum cost you set for the run, so a long list never produces a surprise bill.
pageRange (for example 1-3 for abstract and introduction).chunks[].Not in this version. Scanned PDFs contain images rather than text, so the record has status: "no-text-layer" and an empty text; you still get the page count and metadata, and such files are not charged.
PDF stores positioned text fragments, not paragraphs. The Actor rebuilds lines and paragraphs from positions and font sizes, which works well for articles and reports; complex tables and multi-column layouts may come out in a different reading order.
Yes, when you provide the user password in the PDF password input. Without it, or with a wrong one, the file is reported as errorType: "encrypted" and not charged. PDFs that are merely restricted (owner password only) open normally and have metadata.encrypted: true.
Max file size up to 200 MB (default 25), Max pages per PDF up to 5,000 (default 500), text, markdown and pages capped at 2 MB each, 2,000 chunks per PDF, 20 PDFs in parallel, download timeout up to 300 seconds.
The Actor downloads publicly available files exactly like a browser would, at low request rates. You are responsible for respecting the copyright and terms of use of the documents you process.
No. Output fields are stable: existing fields are never renamed or removed without a major version bump announced in the changelog, and new fields are only ever added. You can build integrations on the schema without checking it after every run.
Best Damn PDF Text Extractor plugs into the tools you already use through Apify integrations, so results can flow on without anyone downloading a file. Ready-made connectors include:
You can also attach webhooks to trigger your own endpoint whenever a run succeeds, fails or times out. For example, send extracted text straight into your vector database or RAG pipeline, or archive it in Google Drive.
Found a PDF that extracts badly, or need OCR or table extraction? Open a ticket in the Issues tab. The source is MIT licensed; parsing is powered by pdf.js (Apache-2.0). If this Actor saved you time, a review on its Store page helps other people find it.
The full source code is on GitHub: josh99smith/pdf-text-extractor. Stars and pull requests are welcome.
blockednetworktimeoutparse-error