Melious
Tools

Document conversion

Convert PDF, DOCX, XLSX, PPTX, and image files to markdown with automatic OCR. Batch processing and real-time streaming progress.

POST /v1/documents/convert

Quick Start

import httpx

API_KEY = "sk-mel-<YOUR_API_KEY>"

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>';

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>" \
  -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 TierDaily Page Limit
Free, Anonymous500 pages/day
Plus, Explorer, Pro, Edu2,000 pages/day
Max, Business, Business Plus5,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>

Query Parameters

ParameterTypeDefaultDescription
streambooleanfalseEnable SSE streaming for real-time progress
force_ocrbooleanfalseForce OCR even for text-extractable documents

Supported File Formats

CategoryFormatsNotes
PDF.pdfText extraction + OCR for scanned pages
Microsoft Office.docx, .doc, .xlsx, .xls, .pptx, .pptFull formatting preserved
OpenDocument.odt, .ods, .odpLibreOffice/OpenOffice formats
Spreadsheets.csvComma-separated values
Rich Text.rtfRich text format
Images.png, .jpg, .gif, .webp, .bmp, .tiff, .heic, .avifOCR applied automatically
Text.txt, .md, .html, .xmlConverted 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

FieldTypeDescription
resultsarrayArray of document results
results[].filenamestringOriginal filename
results[].pagesarrayArray of page objects
results[].pages[].page_numberintegerPage number (1-indexed)
results[].pages[].contentstringMarkdown content
results[].pages[].metadataobjectOptional page metadata
results[].total_pagesintegerTotal pages in document
results[].file_typestringDetected MIME type
results[].statusstringsuccess or error
results[].errorstringError message (if status is error)
results[].processing_time_msintegerProcessing time in milliseconds
totalintegerTotal documents submitted
successfulintegerNumber of successful conversions
failedintegerNumber of failed conversions
processing_time_msintegerTotal processing time

Streaming Response

Enable streaming for real-time progress updates on large documents or batches:

POST /v1/documents/convert?stream=true

SSE 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>"

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>';

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>" \
  -F "files=@large_report.pdf"

Multiple Files

Convert multiple documents in a single request:

import httpx

API_KEY = "sk-mel-<YOUR_API_KEY>"

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>';

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>" \
  -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>" \
  -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

CodeDescription
VALIDATION_REQUIRED_FIELDNo files provided
AUTH_SESSION_REQUIREDNo valid authentication
AUTH_INSUFFICIENT_SCOPEAPI key missing kit.documents scope
KIT_DOCUMENT_RATE_LIMITEDDaily page limit exceeded (session auth)
BILLING_INSUFFICIENT_ENERGYInsufficient balance (API key auth)
KIT_6202Document parsing failed
SYSTEM_SERVICE_UNAVAILABLEDocument 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

  1. Use streaming for large files - Get real-time progress updates instead of waiting for the entire batch
  2. Batch related documents - Process multiple files in a single request to reduce overhead
  3. Check file types - Ensure files are in supported formats before uploading
  4. Handle partial failures - When processing multiple files, some may succeed while others fail
  5. Use force_ocr wisely - Only enable when text extraction quality is poor

See Also

On this page