# SiteVerdict — Office Docs to Markdown for AI (DOCX, PPTX, XLSX) (`siteverdict/siteverdict-doc-to-markdown`) Actor

Convert DOCX, PPTX, XLSX/XLS/CSV, ODT/ODS/ODP, EPUB and HTML files (URLs or key-value store) into clean Markdown with headings and tables kept, plus RAG chunks, sheet JSON, speaker notes and metadata. Zip-bomb safe, no LLM needed.

- **URL**: https://apify.com/siteverdict/siteverdict-doc-to-markdown.md
- **Developed by:** [Aman Thakran](https://apify.com/siteverdict) (community)
- **Categories:** AI, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## SiteVerdict — Office Docs to Markdown for AI (DOCX, PPTX, XLSX)

**Turn Word, PowerPoint, Excel, OpenDocument, EPUB and HTML files into clean, LLM-ready Markdown — with headings, lists and tables intact — plus ready-to-embed RAG chunks. Deterministic, no AI guessing, no data sent to an LLM.**

Most "document to Markdown" tools flatten tables into a wall of text, lose slide order, ignore speaker notes or choke on spreadsheets. This Actor converts each format with a purpose-built parser:

| Format | What you get |
|---|---|
| **DOCX** (Word) | headings (H1–H6 from Word styles), lists, bold/italic, links, **real Markdown tables**, footnotes, image alt text; title/author from document properties |
| **PPTX** (PowerPoint) | `## Slide N: Title` in presentation order, bullets with indent levels, **tables**, picture descriptions, **speaker notes** |
| **XLSX / XLS / ODS / CSV** | one Markdown table per sheet (formatted values, dates), hidden sheets skipped, optional **rows as JSON keyed by header** |
| **ODT / ODP** (LibreOffice) | headings, lists, tables, footnotes, links; slides + notes |
| **EPUB** | chapters in reading (spine) order, title/author/language, tables, image alt text |
| **HTML** | main content only (menus, footers, cookie banners removed) |
| **Markdown / text** | passed through and chunked |

Every document also gets **word count, token estimate, SHA-256 hash, heading outline, heading-aware RAG chunks** and an optional `.md` file in the key-value store.

### Who it is for

- **RAG and AI-agent builders** loading policy documents, decks and spreadsheets into a knowledge base.
- **Ops and compliance teams** who need searchable Markdown copies of Word/Excel templates and policies.
- **Developers** who want a "file in, Markdown out" API without running LibreOffice or Pandoc.

### Three typical jobs

1. **Policy library → knowledge base**: 300 DOCX policies → Markdown + 512-token chunks → feed SiteVerdict Vector DB Loader (dataset mode).
2. **Sales decks → searchable notes**: PPTX with speaker notes → one Markdown file per deck, slide by slide.
3. **Excel → JSON**: `includeSheetJson: true` gives each sheet's rows as JSON objects keyed by the header row.

### Quick start

```json
{
  "documentUrls": [
    { "url": "https://assets.publishing.service.gov.uk/media/6644c166b7249a4c6e9d3558/School_music_development_plan_summary_template.docx" },
    { "url": "https://assets.publishing.service.gov.uk/media/5fb41673d3bf7f63da090f11/Discount_Factors.xlsx" }
  ]
}
```

Files you uploaded yourself: put them in a key-value store and pass `keyValueStoreId` + `keyValueStoreKeys`.

### Before / after

PowerPoint slide (title "Fares", a 2×2 table, a picture with alt text, speaker notes) becomes:

```markdown
### Slide 2: Fares

| Route | Fare |
| --- | --- |
| LHR-SYD | £2,100 |

[Image: Map of routes]

> **Speaker notes:** Mention the Sydney launch.
```

### Output (one dataset item per document)

```json
{
  "source": "https://assets.publishing.service.gov.uk/.../Discount_Factors.xlsx",
  "status": "ok",
  "format": "xlsx",
  "title": null,
  "markdown": "## Sheet: Cover\n\n| Green Book Supplementary Guidance |\n| --- |\n| Discount Factors |\n...\n\n## Sheet: Standard Discount Factors\n\n| ... | Year | Discount <br>Rate | Discount <br>Factor |\n...",
  "wordCount": 815,
  "tokenEstimate": 2006,
  "headings": [{ "level": 2, "text": "Sheet: Cover" }, { "level": 2, "text": "Sheet: Standard Discount Factors" }],
  "stats": { "tables": 3, "sheets": 3 },
  "sheets": [{ "name": "Cover", "rowCount": 11, "columnCount": 1, "truncated": false }],
  "chunkCount": 7,
  "chunks": [{ "chunkIndex": 0, "headingPath": ["Sheet: Cover"], "text": "Sheet: Cover\n\n## Sheet: Cover\n\n| Green Book Supplementary Guidance | ..." }],
  "markdownFileKey": "doc-0002-Discount_Factors.md",
  "warnings": [],
  "charged": "document-converted"
}
```

Other statuses (never charged): `skipped_budget` (your maximum total charge for the run did not cover this document; every unprocessed document still gets a row, and smaller documents are still tried after a large one that does not fit), `unsupported` (PDF, legacy .doc/.ppt, unknown), `empty` (no text — e.g. scanned images, use SiteVerdict Scanned Doc OCR), `rejected` (zip bomb / malformed archive), `blocked` (robots.txt, private address, 401/403), `paywalled`, `error`.

### Pricing (pay per event)

| Event | Price | When |
|---|---|---|
| `document-converted` | $0.003 | a document converted to non-empty Markdown |
| `large-document-converted` | $0.01 | instead of the above when the output exceeds ~50,000 tokens (books, huge workbooks) |

Examples: **100 Word/PowerPoint files ≈ $0.30**; 1,000 mixed office files ≈ $3; a 400-page EPUB ≈ $0.01. Failed, unsupported and empty files are free.

### FAQ

**Is any content sent to an AI model?** No. Conversion is deterministic and local to the run.

**PDFs?** Not in this Actor. Text PDFs need a PDF extractor; scanned PDFs need OCR (SiteVerdict Scanned Doc OCR).

**Formulas in Excel?** You get the cell's displayed (formatted) value, not the formula.

**Merged cells / complex layouts?** A merged value is kept once, in its first (top-left) cell, and the cells it covers (across or down) become empty cells, so every column stays under its own header; DOCX results with merged cells or nested tables (output as text) say so in `warnings`; text boxes and SmartArt in Word are converted as text where the file stores it as text.

**Can my agent call this?** Yes — MCP tool `convert_document_to_markdown` (see MCP-TOOL.md).

### Limits

- Max file size 50 MB by default (up to 200 MB), 1,000 documents per run, 100,000 rows per sheet, 20 million Markdown characters per document.
- Zip-bomb guards: max 20,000 archive entries, 400 MB uncompressed, suspicious compression ratios refused.
- Not supported: PDF, legacy binary .doc/.ppt, password-protected files, embedded OLE objects, charts (chart titles may appear as text), images (alt text only).
- Word lists are converted when the document uses Word numbering; manually typed bullets stay as typed.

### Sources & compliance

- **Your files:** processed only for this run; nothing is kept beyond your run's storage. Documents fetched from URLs are downloaded with an honest User-Agent (`SiteVerdict/doc-to-markdown (+hello@webrevenuescan.com)`), robots.txt respected, SSRF guard on every redirect, size and time limits.
- **Example documents** in the prefill are Crown copyright, published on GOV.UK under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/ , checked 2026-10-11).
- **Libraries (permissive only):** mammoth (BSD-2-Clause), SheetJS Community Edition 0.20.3 (Apache-2.0, from cdn.sheetjs.com), JSZip (MIT option of its MIT/GPL dual licence), linkedom (ISC), turndown (MIT), @mozilla/readability (Apache-2.0).
- **Personal data / UK GDPR:** documents can contain personal data. You decide what you upload and remain the controller; the Actor does not search for or extract personal data specifically. Not legal advice.

Built by SiteVerdict (BharatFare Travel and Technologies Ltd, UK). Contact: hello@webrevenuescan.com.

# Changelog

This Actor's version history is a separate document: https://apify.com/siteverdict/siteverdict-doc-to-markdown/changelog.md

# Actor input Schema

## `documentUrls` (type: `array`):

Public links to .docx, .pptx, .xlsx, .xls, .csv, .odt, .ods, .odp, .epub, .html or .md files. robots.txt is respected; login-protected links will fail.

## `keyValueStoreId` (type: `string`):

Convert files you uploaded to an Apify key-value store. Give its ID or name, then the record keys below.

## `keyValueStoreKeys` (type: `array`):

Keys of the records (files) to convert from that store.

## `includeChunks` (type: `boolean`):

Heading-aware chunks with heading path; tables and code kept whole when possible.

## `chunkSize` (type: `integer`):

Target maximum tokens per chunk.

## `chunkOverlap` (type: `integer`):

Overlap carried within the same section.

## `includeLinks` (type: `boolean`):

Off = fewer tokens.

## `includeSpeakerNotes` (type: `boolean`):

Appends each slide's notes as a quote.

## `includeHiddenSheets` (type: `boolean`):

Hidden sheets are skipped by default.

## `maxSheetRows` (type: `integer`):

Larger sheets are truncated (flagged in the output).

## `includeSheetJson` (type: `boolean`):

Each sheet's rows as objects keyed by the header row (Excel to JSON).

## `includePlainText` (type: `boolean`):

Adds a 'text' field (Markdown stripped).

## `htmlExtraction` (type: `string`):

'auto' keeps the main content (drops menus/footers); 'full-page' keeps everything except navigation.

## `saveMarkdownFiles` (type: `boolean`):

Stored in the run's key-value store (doc-0001-<name>.md).

## `maxFileSizeMb` (type: `integer`):

Bigger files are refused (not charged).

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

Documents converted in parallel.

## `requestTimeoutSecs` (type: `integer`):

Per file.

## Actor input object example

```json
{
  "documentUrls": [
    {
      "url": "https://assets.publishing.service.gov.uk/media/6644c166b7249a4c6e9d3558/School_music_development_plan_summary_template.docx"
    },
    {
      "url": "https://assets.publishing.service.gov.uk/media/5fb41673d3bf7f63da090f11/Discount_Factors.xlsx"
    }
  ],
  "keyValueStoreKeys": [],
  "includeChunks": true,
  "chunkSize": 512,
  "chunkOverlap": 64,
  "includeLinks": true,
  "includeSpeakerNotes": true,
  "includeHiddenSheets": false,
  "maxSheetRows": 5000,
  "includeSheetJson": false,
  "includePlainText": false,
  "htmlExtraction": "auto",
  "saveMarkdownFiles": true,
  "maxFileSizeMb": 50,
  "maxConcurrency": 3,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `output` (type: `string`):

No description

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

No description

# 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 = {
    "documentUrls": [
        {
            "url": "https://assets.publishing.service.gov.uk/media/6644c166b7249a4c6e9d3558/School_music_development_plan_summary_template.docx"
        },
        {
            "url": "https://assets.publishing.service.gov.uk/media/5fb41673d3bf7f63da090f11/Discount_Factors.xlsx"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("siteverdict/siteverdict-doc-to-markdown").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 = { "documentUrls": [
        { "url": "https://assets.publishing.service.gov.uk/media/6644c166b7249a4c6e9d3558/School_music_development_plan_summary_template.docx" },
        { "url": "https://assets.publishing.service.gov.uk/media/5fb41673d3bf7f63da090f11/Discount_Factors.xlsx" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("siteverdict/siteverdict-doc-to-markdown").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 '{
  "documentUrls": [
    {
      "url": "https://assets.publishing.service.gov.uk/media/6644c166b7249a4c6e9d3558/School_music_development_plan_summary_template.docx"
    },
    {
      "url": "https://assets.publishing.service.gov.uk/media/5fb41673d3bf7f63da090f11/Discount_Factors.xlsx"
    }
  ]
}' |
apify call siteverdict/siteverdict-doc-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,siteverdict/siteverdict-doc-to-markdown"
        }
    }
}
```

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/lQgKDQAXQg5Otdhff/builds/30a4WOSKsTXyEa1Ac/openapi.json
