PDF, Word, Excel, images, audio, email — all converted to clean Markdown (or DOCX, CSV, EPUB, and more) with a single HTTP call. AI-enhanced quality, async webhooks, batch processing.
Upload a file and get structured Markdown back in under 5 minutes.
# 1. Create an API key at scanheroai.com/settings/api-keys
# 2. Upload a PDF and get Markdown back in seconds
curl -X POST https://api.scanheroai.com/v1/tasks \
-H "X-Api-Key: sh_your_key" \
-F "file=@report.pdf" \
-F 'options_json={"output_format":"markdown"}'$ pip install scanhero
from scanhero import ScanHero
sh = ScanHero(api_key="sh_...")
# Small files return immediately
task = sh.tasks.create("report.pdf")
print(task.output_markdown)
# Large files are async — poll until done
task = sh.tasks.create("recording.mp4")
task = sh.tasks.wait(task.task_id)
# Refine with an LLM prompt
task = sh.tasks.adjust(task.task_id, "Summarise in bullet points")
# Download as DOCX
docx = sh.tasks.download(task.task_id, format="docx")
open("output.docx", "wb").write(docx)$ npm install @scanhero/sdk
import { ScanHero } from "@scanhero/sdk";
const sh = new ScanHero({ apiKey: "sh_..." });
// or: const sh = ScanHero.fromEnv(); // reads SCANHERO_API_KEY
// Upload a PDF
const task = await sh.tasks.createTaskV1TasksPost({
formData: {
file: new Blob([pdfBytes], { type: "application/pdf" }),
options_json: JSON.stringify({ output_format: "markdown" }),
},
});
// Poll until done (large files)
let result = task;
while (result.status === "pending" || result.status === "processing") {
await new Promise((r) => setTimeout(r, 2000));
result = await sh.tasks.getTaskV1TasksTaskIdGet({ taskId: task.task_id });
}
console.log(result.output_markdown);
// Refine with an LLM prompt
await sh.tasks.adjustTaskV1TasksTaskIdAdjustPost({
taskId: task.task_id,
requestBody: { prompt: "Summarise in bullet points" },
});
// Profile and credit balance
const me = await sh.users.getMeV1UsersMeGet();
console.log(me.credits);Every request must include your API key in the X-Api-Key header.
X-Api-Key: sh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA task is one document conversion. Upload any supported file via POST /v1/tasks — not one endpoint per format. See Tasks vs direct convert for the few exceptions (DjVu and legacy PPT).
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/tasks | Upload a file and start conversion |
| GET | /v1/tasks/{id} | Poll status and retrieve output |
| GET | /v1/tasks | List your recent tasks |
| POST | /v1/tasks/{id}/adjust | Refine output with an LLM prompt |
| GET | /v1/tasks/{id}/download | Download output in a specific format |
| GET | /v1/tasks/estimate-cost | Preview credit cost before uploading |
The interactive reference shows only three routes under the convert tag — that is intentional. Almost every file type goes through POST /v1/tasks (plus optional download). The convert endpoints are binary passthrough shortcuts that skip Markdown and LLM processing.
| Input / goal | What you get | API route |
|---|---|---|
| PDF, DOCX, DOC, RTF, ODT | Structured documents → Markdown or other formats | POST /v1/tasks |
| XLSX, XLS, ODS, CSV | Spreadsheets → Markdown, CSV, XLSX, … | POST /v1/tasks |
| PPTX (modern) | Presentations → Markdown or download as other formats | POST /v1/tasks |
| PNG, JPEG, TIFF, WEBP, HEIC, GIF, SVG | Images → Markdown (OCR) or sidecar | POST /v1/tasks |
| MP3, WAV, M4A, MP4, WebM, … | Audio / video → transcript Markdown | POST /v1/tasks |
| EML, MBOX, PST, MSG | Email archives → Markdown per message | POST /v1/tasks |
| EPUB, HTML, LaTeX, DjVu input | Books, web captures, TeX sources | POST /v1/tasks |
| Any completed task | Re-export as DOCX, PDF, EPUB, LaTeX, SRT, RST, … | GET /v1/tasks/{id}/download?format=… |
| PNG/JPEG/TIFF/WEBP → DjVu only | Direct DjVu compression (no Markdown step) | POST /v1/convert/image-to-djvu |
| PDF → DjVu only | Direct multi-page DjVu (no Markdown step) | POST /v1/convert/pdf-to-djvu |
| Legacy .ppt → PPTX only | LibreOffice passthrough (no Markdown step) | POST /v1/convert/ppt-to-pptx |
Set the initial output via options_json on task create, e.g. {"output_format":"markdown"}. See the tasks and convert sections in the OpenAPI reference for request fields.
Register a URL to receive push notifications when tasks complete. Ideal for async (large file) processing.
Register a webhook
curl -X POST https://api.scanheroai.com/v1/webhooks \
-H "X-Api-Key: sh_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your.app/hooks",
"events": ["task.completed", "task.failed"]
}'Payload you receive
{
"task_id": "3f2a…",
"status": "done",
"output_markdown": "# Invoice\n\n**Date:** 2026-01-15…",
"quality_score": 0.97,
"credits_used": 10
}Verify authenticity with the X-Scan-Hero-Signature header (HMAC-SHA256).
Prefer a typed client over raw HTTP? We ship official SDKs for the two most common stacks. Both authenticate with X-Api-Key and cover the same REST surface as this documentation. Source lives in the repo under sdk/python and sdk/typescript.
| Language | Package | Install | Notes |
|---|---|---|---|
| Python 3.9+ | scanhero | pip install scanhero | Handcrafted helpers — tasks.wait(), retries, file uploads from path/bytes |
| TypeScript / JS | @scanhero/sdk | npm install @scanhero/sdk | Auto-generated from /openapi.json — full typed API, Node 18+ fetch |
Official SDK with full type hints, automatic retry on 429/5xx, and built-in async polling. Built on httpx (single dependency).
from scanhero import ScanHero
sh = ScanHero(api_key="sh_...")
# Small files return immediately
task = sh.tasks.create("report.pdf")
print(task.output_markdown)
# Large files are async — poll until done
task = sh.tasks.create("recording.mp4")
task = sh.tasks.wait(task.task_id)
# Refine with an LLM prompt
task = sh.tasks.adjust(task.task_id, "Summarise in bullet points")
# Download as DOCX
docx = sh.tasks.download(task.task_id, format="docx")
open("output.docx", "wb").write(docx)Generated with openapi-typescript-codegen from the live OpenAPI spec. Every endpoint, model, and enum is typed. Works in Node.js and modern browsers.
import { ScanHero } from "@scanhero/sdk";
const sh = new ScanHero({ apiKey: "sh_..." });
// or: const sh = ScanHero.fromEnv(); // reads SCANHERO_API_KEY
// Upload a PDF
const task = await sh.tasks.createTaskV1TasksPost({
formData: {
file: new Blob([pdfBytes], { type: "application/pdf" }),
options_json: JSON.stringify({ output_format: "markdown" }),
},
});
// Poll until done (large files)
let result = task;
while (result.status === "pending" || result.status === "processing") {
await new Promise((r) => setTimeout(r, 2000));
result = await sh.tasks.getTaskV1TasksTaskIdGet({ taskId: task.task_id });
}
console.log(result.output_markdown);
// Refine with an LLM prompt
await sh.tasks.adjustTaskV1TasksTaskIdAdjustPost({
taskId: task.task_id,
requestBody: { prompt: "Summarise in bullet points" },
});
// Profile and credit balance
const me = await sh.users.getMeV1UsersMeGet();
console.log(me.credits);Services are namespaced on the client: sh.tasks, sh.jobs, sh.webhooks, sh.templates, and more — mirroring the REST paths below.
When the API changes, regenerate typed clients from /openapi.json:
# TypeScript (repo: sdk/typescript)
cd sdk/typescript && npm run generate
# Against local backend:
OPENAPI_URL=http://127.0.0.1:8080/openapi.json npm run generate# Python (optional codegen output)
pip install openapi-python-client
openapi-python-client generate \
--url https://api.scanheroai.com/openapi.json \
--output-path sdk/python-generated
# Handcrafted SDK: edit sdk/python/ directlyNew accounts start with 100 free credits. Paid plans add more.
| Operation | Cost |
|---|---|
| Document conversion (any format, ≤5 MB) | 10 credits |
| Overage | +1 credit / MB above 5 MB |
| Audio / Video transcription | 10 + 2 credits / minute |
| Email batch (.mbox, .pst) | 10 + 1 credit / email |
| LLM adjustment round | token-priced (min. 1 credit; typically 1–10+ per round) |
| Batch job (N files) | 10 × N upfront |
Start with 100 free credits. No credit card required.