Documentation

Everything you need to use Openkova.

Conversion modes

Openkova supports three ways to produce screenshots:

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.

RequestContent-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.

RequestContent-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   // optional

Done 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 --help

MCP 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"]
    }
  }
}