Steps
- Collect public document links. PDF, DOCX, PPTX, XLSX/XLS, HTML, CSV or TXT links. Google Docs, Drive, Dropbox and GitHub share links work if shared publicly.
- Pick a chunk strategy. Use size (heading-aware, about 512 tokens with 50 overlap) for most RAG work; section, page or none for special cases.
- Run the conversion. Call sourcewire/doc-to-markdown through the Apify API, a client library or MCP.
- Keep only successful items. Check status: ok items have chunks; error and needs_ocr items explain why and are not charged.
- Embed chunks with their metadata. Store each chunk's text with url, page, pageEnd and headingPath so answers can cite their source.
- Skip unchanged files next time. Compare each document's sha256 with the last run and only re-embed documents that changed.
Choosing a chunk strategy
chunkStrategy | Use it when |
|---|---|
size (default) | General RAG. Heading-aware chunks of about chunkSize tokens (default 512) with chunkOverlap (default 50). Tables are never cut mid-row. |
section | Documents with strong headings, like policies or manuals, where one section is one answer. |
page | You need page-level citations, or one chunk per slide or spreadsheet sheet. |
none | You only want the full Markdown, for example to summarize a document in one prompt. |
Long tables are split by rows with the header row repeated in each chunk, so every chunk can be read on its own.
Convert and prepare chunks in Python
# pip install apify-client
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("sourcewire/doc-to-markdown").call(run_input={
"urls": [
"https://www.irs.gov/pub/irs-pdf/fw9.pdf",
# add your own public links here
],
"chunkStrategy": "size",
"chunkSize": 512,
"chunkOverlap": 50,
"includeMarkdown": False, # chunks only, smaller response
})
records = []
for doc in client.dataset(run["defaultDatasetId"]).iterate_items():
if doc["status"] != "ok":
print("skipped", doc["url"], doc["errorCode"]) # not charged
continue
for ch in doc["chunks"]:
records.append({
"id": f'{doc["sha256"]}-{ch["index"]}',
"text": ch["text"],
"metadata": {
"url": doc["url"],
"title": doc.get("title"),
"page": ch.get("page"),
"pageEnd": ch.get("pageEnd"),
"headingPath": ch.get("headingPath"),
"hasTable": ch.get("hasTable"),
},
})
print(len(records), "chunks ready to embed")
# Next: embed records[i]["text"] with your embedding model and upsert
# into your vector database along with records[i]["metadata"].
What a chunk looks like
{
"index": 0,
"text": "# W-9\n\n# Request for Taxpayer ...",
"page": 1,
"pageEnd": 1,
"headingPath": "W-9 > Request for Taxpayer",
"hasTable": false,
"charCount": 1890,
"tokenEstimate": 472
}
Prepending headingPath to the text before embedding (for example "W-9 > Request for Taxpayer\n\n" + text) often helps retrieval, because short chunks gain the context of the section they sit in.
Handle failures without paying for them
Every item has the same shape. Check status:
ok: converted and charged once, plus one extra event per additional 50 PDF pages or slides.error: seeerrorCode, such asNOT_FOUND,ACCESS_DENIED,FILE_TOO_LARGE,UNSUPPORTED_FORMAT,ENCRYPTED_PDForTIMEOUT. Not charged.needs_ocr: a scanned PDF with no text layer. Run OCR first. Not charged. Partly scanned PDFs are converted, with the image-only pages listed inscannedPages.
Tips
- Use
includePages: truewhen you want the Markdown of each PDF page, slide or sheet for citations. - Use
estimatedTokensto decide whether a document fits in context or needs chunking. - Use
sha256to skip documents that haven't changed since your last run. - Set
maxPagesandmaxDocuments, or a maximum cost per run in Apify Console, to keep costs predictable.
Cost: $0.005 per converted document, $5 per 1,000. Open Doc to Markdown on the Apify Store.