Skip to content

API Server

Xberg runs as an HTTP REST API server (xberg serve) or as an MCP server (xberg mcp) for AI agent integration.

Bash
# Default: http://127.0.0.1:8000
xberg serve
# Custom host and port
xberg serve -H 0.0.0.0 -p 3000
# With configuration file
xberg serve --config xberg.toml

Extract text from uploaded files via multipart form data.

Field Required Description
files Yes (repeatable) Files to extract
config No JSON config overrides
output_format No plain (default), markdown, djot, or html
Terminal
# Single file
curl -F "files=@document.pdf" http://localhost:8000/extract
# Multiple files
curl -F "files=@doc1.pdf" -F "files=@doc2.docx" http://localhost:8000/extract
# With config overrides
curl -F "files=@scanned.pdf" \
-F 'config={"ocr":{"language":"eng"},"force_ocr":true}' \
http://localhost:8000/extract
Response
{
"results": [
{
"content": "Extracted text...",
"mime_type": "application/pdf",
"metadata": { "page_count": 10, "author": "John Doe" },
"tables": [],
"detected_languages": ["eng"],
"chunks": null,
"images": null
}
],
"errors": [],
"summary": {
"inputs": 1,
"results": 1,
"errors": 0
}
}

Queue an extraction job and return immediately. Accepts the same multipart form data or JSON body as /extract. Returns 202 Accepted with a job identifier. Returns 429 Too Many Requests when the concurrent job limit is reached.

Terminal
curl -F "files=@document.pdf" http://localhost:8000/extract-async
Response (202)
{ "job_id": "550e8400-e29b-41d4-a716-446655440000" }

Poll the status of an async job. state is one of pending, running, completed, or failed. The result field is present only when state == completed; the error field only when state == failed. Jobs expire after 5 minutes and return 404 once evicted.

Terminal
curl http://localhost:8000/jobs/550e8400-e29b-41d4-a716-446655440000
Response
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"state": "completed",
"created_at": "2026-07-07T12:00:00Z",
"updated_at": "2026-07-07T12:00:03Z",
"result": { "results": [], "errors": [], "summary": {} }
}
Endpoint Method Description
/health GET {"status":"healthy","version":"1.0.0-rc.14"}
/version GET {"version":"1.0.0-rc.14"}
/detect POST MIME type detection (multipart)
/formats GET List supported formats
/cache/stats GET Cache statistics
/cache/warm POST Pre-download models
/cache/manifest GET Model manifest with checksums
/cache/clear DELETE Clear all cached files
/info GET {"version":"...","rust_backend":true}
/openapi.json GET OpenAPI 3.1 schema
Python
import asyncio
import json
import httpx
async def main() -> None:
async with httpx.AsyncClient() as client, open("document.pdf", "rb") as f:
response = await client.post(
"http://localhost:8000/extract",
files={"files": f},
)
data = response.json()
print(json.dumps(data, indent=2))
asyncio.run(main())
Error response
{
"error_type": "ValidationError",
"message": "Invalid file format",
"status_code": 400
}
Status Error type Meaning
400 ValidationError Invalid input
422 ParsingError, OcrError Processing failed
500 Internal errors Server errors
Python
import httpx
try:
with httpx.Client() as client:
with open("document.pdf", "rb") as f:
files: dict = {"files": f}
response: httpx.Response = client.post(
"http://localhost:8000/extract", files=files
)
response.raise_for_status()
results: list = response.json()
print(f"Extracted {len(results)} documents")
except httpx.HTTPStatusError as e:
error: dict = e.response.json()
error_type: str = error.get("error_type", "Unknown")
message: str = error.get("message", "No message")
print(f"Error: {error_type}: {message}")

The server discovers xberg.toml in the current and parent directories. Pass --config path/to/file to use a different file.

Variable Default Description
XBERG_MAX_REQUEST_BODY_BYTES 104857600 Max request body size in bytes
XBERG_MAX_MULTIPART_FIELD_BYTES 104857600 Max multipart field size in bytes
XBERG_CORS_ORIGINS * Comma-separated allowed origins

See Configuration Guide for all options.


Terminal
xberg mcp
xberg mcp --config xberg.toml
Python
import subprocess
import time
from typing import Optional
mcp_process: subprocess.Popen = subprocess.Popen(
["python", "-m", "xberg", "mcp"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
pid: Optional[int] = mcp_process.pid
print(f"MCP server started with PID: {pid}")
time.sleep(1)
print("Server is running, listening for connections")

The MCP server exposes extract, extract_batch, detect_mime_type, list_formats, get_version, and the cache_* tools. See the MCP Reference for the full tool list, parameters, and schemas.

All extraction tools accept an optional config object. URI and byte payload details live in ExtractInput as kind = "uri" or kind = "bytes".

Python
import asyncio
import json
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main() -> None:
server_params: StdioServerParameters = StdioServerParameters(
command="xberg", args=["mcp"]
)
inputs: list[dict[str, str]] = [
{"kind": "uri", "uri": "file1.pdf"},
{"kind": "uri", "uri": "file2.docx"},
{"kind": "uri", "uri": "notes.md"},
]
config: dict[str, bool] = {"use_cache": True}
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(
"extract_batch",
arguments={"inputs": inputs, "config": config},
)
payload_text: str = result.content[0].text
batch: dict = json.loads(payload_text)
print(f"Extracted {batch['summary']['results']} files")
for index, item in enumerate(batch["results"], start=1):
mime_type: str | None = item.get("mime_type")
preview: str = item["content"][:80].replace("\n", " ")
print(f" [{index}] {mime_type or 'unknown'}: {preview}...")
asyncio.run(main())

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
"mcpServers": {
"xberg": {
"command": "xberg",
"args": ["mcp"]
}
}
}

For container deployment, see the Docker Guide.