Documentation
Everything you need to use Openkova.
Conversion modes
Openkova supports three ways to produce screenshots:
- HTML Snippet — Paste raw HTML into the textarea. The server wraps it in a full document, renders it in a headless Chromium browser, and returns a screenshot or PDF in your chosen format (PNG, JPEG, WebP, or PDF).
- Files — Upload one or more
.htmlor.htmfiles. Each file is rendered individually and returned as a separate file, processed in order. - URL / Crawl — Provide any public
http/httpsURL. Openkova fetches the page, extracts all same-origin links (up to 200), and screenshots up to 10 pages at a time (depth 1 follows direct links; depth 2 follows their links once more). Private and internal hosts are blocked. Pages are captured sequentially.
Sessions
Every conversion is associated with a session — a UUID that groups your screenshots together. The session ID is returned in every API response and stored in an openkova_session HTTP-only cookie (7-day expiry). Pass it back as sessionId in subsequent requests to keep results in the same gallery. If you omit it, a new session is created automatically.
Storage retention: 24 hours. Screenshots are automatically deleted 24 hours after the session was last written to. The server runs a cleanup pass every hour. Download your images before then — use the Download All button or the GET /api/session/:sessionId/download endpoint to grab everything as a ZIP.
Streaming responses (SSE)
All three convert endpoints respond with a Server-Sent Events (SSE) stream rather than a single JSON blob. This lets the UI (and your own code) display live progress as the browser launches, pages load, and snapshots are taken.
Each line in the stream is a JSON event in one of three shapes:
// A step in progress
{ "type": "progress", "message": "Launching virtual browser" }
// Final success — stream closes after this
{ "type": "done", "message": "Done — 3 screenshots saved", "data": { ... } }
// Unrecoverable error — stream closes after this
{ "type": "error", "message": "Conversion failed" }The data field on done events contains the same payload that was previously returned as JSON (see per-endpoint details below).
The Set-Cookie header for the session is set on the streaming response itself, so cookies work normally even though there is no JSON body.
Consuming the stream (JavaScript)
const res = await fetch('/api/convert/snippet', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: '<h1>Hello</h1>' }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE events are separated by double newline
const parts = buffer.split('\n\n');
buffer = parts.pop() ?? '';
for (const part of parts) {
const line = part.split('\n').find(l => l.startsWith('data: '));
if (!line) continue;
const event = JSON.parse(line.slice(6));
if (event.type === 'progress') console.log(event.message);
if (event.type === 'done') console.log('Result:', event.data);
if (event.type === 'error') console.error(event.message);
}
}POST /api/convert/snippet
Renders a raw HTML string and returns a single screenshot or PDF. The HTML is wrapped in a minimal full document with a reset stylesheet before rendering.
Request — Content-Type: application/json
{
"html": "<h1>Hello</h1>", // required — raw HTML string
"format": "png", // optional — "png" (default), "jpeg", "webp", "pdf"
"viewport": { "width": 1280, "height": 800 }, // optional
"fullPage": false, // optional — capture full scrollable height
"sessionId": "uuid" // optional — omit to create a new session
}Done event data:
{
"sessionId": "uuid",
"imageId": "uuid.png",
"url": "/api/image/{sessionId}/{imageId}"
}POST /api/convert/file
Accepts one or more HTML files as multipart form data and returns one screenshot per file. Files are rendered sequentially in the order they are submitted.
Request — Content-Type: multipart/form-data
files File[] // required — one or more .html/.htm files
format string // optional — "png" (default), "jpeg", "webp", "pdf"
viewport string // optional — JSON: {"width":1280,"height":800}
fullPage string // optional — "true" or "false"
sessionId string // optionalDone event data:
{
"sessionId": "uuid",
"results": [
{ "imageId": "uuid", "filename": "index.html", "url": "/api/image/..." },
...
]
}POST /api/convert/url
Crawls a public URL and screenshots discovered pages 10 at a time. Only http/https URLs are accepted; private and internal hosts are blocked. Crawls are capped at 200 URLs per call.
Request (crawl mode) — Content-Type: application/json
{
"url": "https://example.com", // required — http/https only, public host
"depth": 1, // optional — 1 (default) or 2
"format": "png", // optional — "png" (default), "jpeg", "webp", "pdf"
"viewport": { "width": 1280, "height": 800 }, // optional
"fullPage": false, // optional
"sessionId": "uuid" // optional
}Request (paginate mode) — pass pre-known URLs to screenshot without re-crawling:
{
"urls": ["https://example.com/page-11", ...], // required
"format": "png", // optional
"viewport": { ... }, // optional
"fullPage": false, // optional
"sessionId": "uuid", // required — use the session from the first request
"offset": 10, // how many pages were already captured
"total": 25 // total discovered in the original crawl
}Done event data:
{
"sessionId": "uuid",
"results": [{ "imageId": "uuid", "url": "https://example.com/" }, ...],
"remaining": ["https://example.com/page-11", ...],
"total": 25
}GET /api/image/:sessionId/:id
Returns a single screenshot or PDF. The Content-Type reflects the file format (image/png, image/jpeg, image/webp, or application/pdf). Responses are cached for 1 hour (Cache-Control: public, max-age=3600, immutable). Returns 404 if the image does not exist.
GET /api/session/:sessionId/download
Downloads all screenshots in a session as a single .zip file. Returns 404 if the session has no images.
GET /api/session/:sessionId
Returns all image IDs associated with a session.
{ "images": ["uuid1", "uuid2", ...] }CLI (@openkova/cli)
Use Openkova from the terminal or CI/CD pipelines without running the web app. The kova binary supports all input modes and output formats.
npm install -g @openkova/cli
kova --helpMCP server (@openkova/mcp)
A local MCP server that connects any MCP-compatible AI client to your machine's Chromium. Exposes three tools: screenshot_url, screenshot_snippet, and crawl_url. Screenshots are returned as inline images so the AI can see the result directly.
Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"openkova": {
"command": "npx",
"args": ["-y", "@openkova/mcp"]
}
}
}Cursor / Windsurf — add to your .cursor/mcp.json or .windsurf/mcp.json:
{
"mcpServers": {
"openkova": {
"command": "npx",
"args": ["-y", "@openkova/mcp"]
}
}
}