Document Conversion
Convert documents to markdown with OCR support
Document Conversion
Convert PDF, DOCX, XLSX, PPTX, and image files to markdown format with automatic OCR support. The API supports both batch processing and real-time streaming progress updates.
POST /v1/documents/convertQuick Start
import httpx
API_KEY = "sk-mel-your-api-key-here"
async def convert_document():
async with httpx.AsyncClient() as client:
with open("report.pdf", "rb") as f:
response = await client.post(
"https://api.melious.ai/v1/documents/convert",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"files": ("report.pdf", f, "application/pdf")}
)
result = response.json()
print(result["results"][0]["pages"][0]["content"])import fs from 'fs';
import FormData from 'form-data';
const API_KEY = 'sk-mel-your-api-key-here';
async function convertDocument() {
const form = new FormData();
form.append('files', fs.createReadStream('report.pdf'));
const response = await fetch('https://api.melious.ai/v1/documents/convert', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
},
body: form
});
const result = await response.json();
console.log(result.results[0].pages[0].content);
}curl https://api.melious.ai/v1/documents/convert \
-H "Authorization: Bearer sk-mel-your-api-key-here" \
-F "files=@report.pdf"Authentication
The Document Conversion API supports two authentication methods:
Session Authentication (Free)
When logged in via the web interface, document conversion is free but rate-limited based on your plan tier:
| Plan Tier | Daily Page Limit |
|---|---|
| Free, Anonymous | 500 pages/day |
| Plus, Explorer, Pro, Edu | 2,000 pages/day |
| Max, Business, Business Plus | 5,000 pages/day |
API Key Authentication (Paid)
API key users are charged per page with no daily limits:
- Rate: €0.50 per 1,000 pages (€0.0005 per page)
- Scope Required:
kit.documents
Create an API key with kit.documents scope at melious.ai/account/api/keys.
Request Format
Multipart Form Data
Upload files using multipart/form-data encoding:
POST /v1/documents/convert
Content-Type: multipart/form-data
Authorization: Bearer sk-mel-your-api-key-hereQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
stream | boolean | false | Enable SSE streaming for real-time progress |
force_ocr | boolean | false | Force OCR even for text-extractable documents |
Supported File Formats
| Category | Formats | Notes |
|---|---|---|
.pdf | Text extraction + OCR for scanned pages | |
| Microsoft Office | .docx, .doc, .xlsx, .xls, .pptx, .ppt | Full formatting preserved |
| OpenDocument | .odt, .ods, .odp | LibreOffice/OpenOffice formats |
| Spreadsheets | .csv | Comma-separated values |
| Rich Text | .rtf | Rich text format |
| Images | .png, .jpg, .gif, .webp, .bmp, .tiff, .heic, .avif | OCR applied automatically |
| Text | .txt, .md, .html, .xml | Converted to markdown |
Response Format
Non-Streaming Response
{
"results": [
{
"filename": "report.pdf",
"pages": [
{
"page_number": 1,
"content": "# Introduction\n\nThis is the first page content...",
"metadata": {
"word_count": 250,
"table_count": 0,
"image_count": 1
}
},
{
"page_number": 2,
"content": "## Chapter 1\n\nDetailed analysis...",
"metadata": {
"word_count": 500,
"table_count": 2
}
}
],
"total_pages": 2,
"file_type": "application/pdf",
"status": "success",
"processing_time_ms": 1234
}
],
"total": 1,
"successful": 1,
"failed": 0,
"processing_time_ms": 1234
}Response Fields
| Field | Type | Description |
|---|---|---|
results | array | Array of document results |
results[].filename | string | Original filename |
results[].pages | array | Array of page objects |
results[].pages[].page_number | integer | Page number (1-indexed) |
results[].pages[].content | string | Markdown content |
results[].pages[].metadata | object | Optional page metadata |
results[].total_pages | integer | Total pages in document |
results[].file_type | string | Detected MIME type |
results[].status | string | success or error |
results[].error | string | Error message (if status is error) |
results[].processing_time_ms | integer | Processing time in milliseconds |
total | integer | Total documents submitted |
successful | integer | Number of successful conversions |
failed | integer | Number of failed conversions |
processing_time_ms | integer | Total processing time |
Streaming Response
Enable streaming for real-time progress updates on large documents or batches:
POST /v1/documents/convert?stream=trueSSE Event Types
The streaming response sends Server-Sent Events (SSE) with the following event types:
queued - Document queued for processing
{
"type": "queued",
"request_id": "abc123",
"filename": "report.pdf",
"position": 0,
"estimated_wait_ms": 0
}processing - Document processing started
{
"type": "processing",
"request_id": "abc123",
"filename": "report.pdf",
"is_ocr": true
}complete - Document conversion completed
{
"type": "complete",
"request_id": "abc123",
"result": {
"filename": "report.pdf",
"pages": [...],
"total_pages": 5,
"file_type": "application/pdf",
"status": "success",
"processing_time_ms": 2500
}
}error - Document conversion failed
{
"type": "error",
"request_id": "abc123",
"filename": "corrupted.pdf",
"error": "Unable to parse PDF structure",
"code": "KIT_6202"
}stream_end - All documents processed
{
"type": "stream_end",
"request_id": "abc123",
"total": 3,
"successful": 2,
"failed": 1,
"processing_time_ms": 5000
}The stream ends with:
data: [DONE]Streaming Example
import httpx
import json
API_KEY = "sk-mel-your-api-key-here"
async def convert_with_streaming():
async with httpx.AsyncClient() as client:
with open("large_report.pdf", "rb") as f:
async with client.stream(
"POST",
"https://api.melious.ai/v1/documents/convert?stream=true",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"files": ("large_report.pdf", f, "application/pdf")},
timeout=300.0
) as response:
async for line in response.aiter_lines():
if line.startswith("data: "):
data = line[6:]
if data == "[DONE]":
print("Stream complete!")
break
event = json.loads(data)
print(f"Event: {event['type']}")
if event["type"] == "complete":
print(f"Converted: {event['result']['filename']}")import fs from 'fs';
import FormData from 'form-data';
const API_KEY = 'sk-mel-your-api-key-here';
async function convertWithStreaming() {
const form = new FormData();
form.append('files', fs.createReadStream('large_report.pdf'));
const response = await fetch(
'https://api.melious.ai/v1/documents/convert?stream=true',
{
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}` },
body: form
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
console.log('Stream complete!');
return;
}
const event = JSON.parse(data);
console.log(`Event: ${event.type}`);
}
}
}
}curl -N https://api.melious.ai/v1/documents/convert?stream=true \
-H "Authorization: Bearer sk-mel-your-api-key-here" \
-F "files=@large_report.pdf"Multiple Files
Convert multiple documents in a single request:
import httpx
API_KEY = "sk-mel-your-api-key-here"
async def convert_multiple():
async with httpx.AsyncClient() as client:
files = [
("files", ("report.pdf", open("report.pdf", "rb"), "application/pdf")),
("files", ("data.xlsx", open("data.xlsx", "rb"), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")),
("files", ("scan.png", open("scan.png", "rb"), "image/png")),
]
response = await client.post(
"https://api.melious.ai/v1/documents/convert",
headers={"Authorization": f"Bearer {API_KEY}"},
files=files
)
result = response.json()
print(f"Converted {result['successful']} of {result['total']} documents")import fs from 'fs';
import FormData from 'form-data';
const API_KEY = 'sk-mel-your-api-key-here';
async function convertMultiple() {
const form = new FormData();
form.append('files', fs.createReadStream('report.pdf'));
form.append('files', fs.createReadStream('data.xlsx'));
form.append('files', fs.createReadStream('scan.png'));
const response = await fetch('https://api.melious.ai/v1/documents/convert', {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}` },
body: form
});
const result = await response.json();
console.log(`Converted ${result.successful} of ${result.total} documents`);
}curl https://api.melious.ai/v1/documents/convert \
-H "Authorization: Bearer sk-mel-your-api-key-here" \
-F "files=@report.pdf" \
-F "files=@data.xlsx" \
-F "files=@scan.png"Force OCR Mode
Force OCR processing even for documents with extractable text (useful for scanned documents saved as text-PDFs):
curl https://api.melious.ai/v1/documents/convert?force_ocr=true \
-H "Authorization: Bearer sk-mel-your-api-key-here" \
-F "files=@scanned_document.pdf"Force OCR mode may increase processing time but can improve quality for documents with poor text extraction.
Error Handling
Error Response
{
"error": {
"code": "VALIDATION_REQUIRED_FIELD",
"message": "At least one file is required"
}
}Error Codes
| Code | Description |
|---|---|
VALIDATION_REQUIRED_FIELD | No files provided |
AUTH_SESSION_REQUIRED | No valid authentication |
AUTH_INSUFFICIENT_SCOPE | API key missing kit.documents scope |
KIT_DOCUMENT_RATE_LIMITED | Daily page limit exceeded (session auth) |
BILLING_INSUFFICIENT_ENERGY | Insufficient balance (API key auth) |
KIT_6202 | Document parsing failed |
SYSTEM_SERVICE_UNAVAILABLE | Document service temporarily unavailable |
Rate Limit Response
When daily limits are exceeded (session auth only):
{
"error": {
"code": "KIT_DOCUMENT_RATE_LIMITED",
"message": "Daily document limit reached (500/500 pages). Resets at midnight UTC.",
"details": {
"used_pages": 500,
"daily_limit": 500,
"remaining_pages": 0,
"reset_at": "2026-01-29T00:00:00Z"
}
}
}Best Practices
- Use streaming for large files - Get real-time progress updates instead of waiting for the entire batch
- Batch related documents - Process multiple files in a single request to reduce overhead
- Check file types - Ensure files are in supported formats before uploading
- Handle partial failures - When processing multiple files, some may succeed while others fail
- Use force_ocr wisely - Only enable when text extraction quality is poor
See Also
- Tools API - Other available tools
- Chat Completions - Use converted documents with AI