@copperline/rendex
v1.5.1
Published
Official TypeScript SDK for Rendex — render HTML or Markdown to images, generate PDFs, capture screenshots, and monitor pages for changes with Rendex Watch
Maintainers
Readme
@copperline/rendex
Official TypeScript SDK for Rendex — the HTML-to-image, PDF, and screenshot rendering API. Turn raw HTML, Markdown, or any webpage into an image or PDF with a single function call.
- Zero runtime dependencies (uses native
fetch) - Full TypeScript types with IntelliSense
- Works in Node.js 18+, Deno, Bun, and browsers
- Typed error handling with API error codes
Install
npm install @copperline/rendex
# or
pnpm add @copperline/rendex
# or
bun add @copperline/rendexQuick Start
import { Rendex } from "@copperline/rendex";
const rendex = new Rendex("your-api-key");
// Render raw HTML straight to a PNG
const { image, metadata } = await rendex.renderHtml("<h1>Hello, world</h1>");
// Or render Markdown (converted to HTML server-side)
const md = await rendex.renderMarkdown("# Hello, world");
// Or capture a live URL
const shot = await rendex.screenshot({
url: "https://example.com",
format: "png",
fullPage: true,
});
// Write to file (Node.js)
import { writeFile } from "node:fs/promises";
await writeFile("screenshot.png", image);
console.log(`${metadata.bytesSize} bytes, loaded in ${metadata.loadTimeMs}ms`);API Reference
new Rendex(apiKey, config?)
Create a new Rendex client.
const rendex = new Rendex("your-api-key", {
baseUrl: "https://api.rendex.dev", // optional, default
});| Parameter | Type | Description |
|-----------|------|-------------|
| apiKey | string | Your Rendex API key. Get one at rendex.dev |
| config.baseUrl | string | Override the API base URL |
| config.fetch | typeof fetch | Custom fetch implementation (for testing) |
rendex.screenshot(options)
Capture a screenshot and return the binary image with metadata.
const { image, metadata } = await rendex.screenshot({
url: "https://example.com",
format: "webp",
width: 1920,
height: 1080,
darkMode: true,
});Returns Promise<ScreenshotResult>:
image—Uint8Arrayof the captured imagemetadata—ScreenshotMetadatawith URL, dimensions, format, bytes, load time, quality signal
rendex.renderHtml(html, options?)
Render raw HTML straight to a PNG (or any format) and return the binary image with metadata. Convenience wrapper over screenshot({ html }).
const { image, metadata } = await rendex.renderHtml("<h1>Hello, world</h1>", {
format: "png",
width: 1200,
});Returns Promise<ScreenshotResult> — same shape as screenshot().
rendex.renderHtmlJson(html, options?)
Render raw HTML and return JSON with a base64-encoded image. Convenience wrapper over screenshotJson({ html }).
const result = await rendex.renderHtmlJson("<h1>Hello, world</h1>");
console.log(result.data.bytesSize);Returns Promise<ScreenshotJsonResponse> — same shape as screenshotJson().
rendex.renderMarkdown(markdown, options?)
Render Markdown straight to a PNG (or any format) and return the binary image with metadata. The Markdown is converted to HTML server-side. Convenience wrapper over screenshot({ markdown }).
const { image, metadata } = await rendex.renderMarkdown("# Hello, world", {
format: "png",
width: 1200,
});Returns Promise<ScreenshotResult> — same shape as screenshot().
rendex.renderMarkdownJson(markdown, options?)
Render Markdown and return JSON with a base64-encoded image. The Markdown is converted to HTML server-side. Convenience wrapper over screenshotJson({ markdown }).
const result = await rendex.renderMarkdownJson("# Hello, world");
console.log(result.data.bytesSize);Returns Promise<ScreenshotJsonResponse> — same shape as screenshotJson().
HTML and Markdown rendering is POST-only and accepts up to 5MB. It is not available via
screenshotUrl()(the GET endpoint can't carry a body). Provide exactly one ofurl,html, ormarkdown.
Mustache data templating
Pass a data object alongside html or markdown to render logic-less Mustache templates before capture. The server substitutes the template variables and then renders the resulting HTML.
// Markdown invoice with a line-item loop
const template = `
# Invoice {{number}}
**Customer:** {{customer}}
| Item | Qty | Price |
|------|-----|-------|
{{#items}}
| {{description}} | {{qty}} | ${{price}} |
{{/items}}
**Total: ${{total}}**
`;
const { image } = await rendex.renderMarkdown(template, {
data: {
number: "INV-2026-042",
customer: "Acme Corp",
items: [
{ description: "Rendex Pro (monthly)", qty: 1, price: "49.00" },
{ description: "Extra render credits", qty: 500, price: "5.00" },
],
total: "54.00",
},
format: "pdf",
});Template syntax:
| Syntax | Description |
|--------|-------------|
| {{var}} | HTML-escaped substitution |
| {{{var}}} | Raw (unescaped) substitution |
| {{#items}}…{{/items}} | Section — iterates an array or renders a truthy block |
| {{^x}}…{{/x}} | Inverted section — renders when x is falsy / empty |
| {{a.b}} | Nested property access |
data is only valid with html or markdown. Passing it alongside url returns a 400 error. The data object is capped at 256KB serialized.
rendex.screenshotJson(options)
Capture a screenshot and return JSON with a base64-encoded image.
const result = await rendex.screenshotJson({
url: "https://example.com",
});
console.log(result.data.image); // base64 string
console.log(result.data.bytesSize); // 45823
console.log(result.meta.usage?.remaining); // 499Returns Promise<ScreenshotJsonResponse> with data (image + metadata) and meta (request ID, usage).
rendex.screenshotUrl(options)
Generate a GET URL for embedding. No network call — pure URL builder.
const url = rendex.screenshotUrl({
url: "https://example.com",
format: "png",
width: 1200,
});
// Use in <img> tags, OpenGraph, etc.Note: The API key is included in the URL. Use server-side only.
Returns string — a complete URL to the screenshot endpoint.
Screenshot Options
All options except url are optional:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| url | string | required | The webpage URL to capture |
| format | "png" \| "jpeg" \| "webp" \| "pdf" | "png" | Output format |
| width | number | 1280 | Viewport width (320–3840) |
| height | number | 800 | Viewport height (240–2160) |
| fullPage | boolean | false | Capture the full scrollable page |
| quality | number | 80 | JPEG/WebP quality (1–100, default 80) |
| delay | number | 0 | Delay before capture in ms (0–10000) |
| darkMode | boolean | false | Emulate dark mode |
| deviceScaleFactor | number | 2 | Device pixel ratio (1–3). 2× Retina by default |
| blockAds | boolean | true | Block ads and trackers |
| blockResourceTypes | string[] | — | Block: "font", "image", "media", "stylesheet", "other" |
| timeout | number | 30 | Page load timeout in seconds (5–60) |
| waitUntil | string | "networkidle2" | Wait strategy: "load", "domcontentloaded", "networkidle0", "networkidle2" |
| waitForSelector | string | — | CSS selector to wait for before capture |
| bestAttempt | boolean | true | Return best-effort screenshot on timeout |
| selector | string | — | Capture a specific element by CSS selector |
Rendex Watch
Monitor a URL on a schedule and get alerted when it changes — real-Chrome visual diff (with a highlighted overlay), an extracted-text diff, or both. One rdx_ key, your shared credit pool.
// Create a watch (an active watch captures its baseline immediately — 1 credit)
const watch = await rendex.createWatch({
url: "https://example.com/pricing",
diffMode: "visual", // "visual" | "text" | "both"
intervalMinutes: 1440, // your plan's floor is the minimum (Free 1440 / Starter 180 / Pro 30 / Ent 5)
webhookUrl: "https://hooks.example.com/rendex", // Starter+; HMAC-signed
notifyEmail: "[email protected]", // any plan; must be your account email
});
// Dry-run a config BEFORE saving — what we'd capture + is the page reachable
const test = await rendex.testWatch({ url: "https://example.com", selector: "#price" });
if (test.reachable) console.log(test.screenshotUrl);
await rendex.listWatches({ status: "active" }); // { items, nextCursor }
await rendex.getWatch(watch.id);
await rendex.runWatch(watch.id); // run a check now → { runId }
const { items } = await rendex.listWatchRuns(watch.id, { limit: 10 });
await rendex.updateWatch(watch.id, { paused: true }); // pause; { paused: false } resumes
await rendex.deleteWatch(watch.id);| Method | Endpoint | Returns |
|--------|----------|---------|
| createWatch(options) | POST /v1/watches | Watch |
| listWatches(query?) | GET /v1/watches | { items: Watch[]; nextCursor } |
| getWatch(id) | GET /v1/watches/:id | Watch |
| updateWatch(id, patch) | PATCH /v1/watches/:id | Watch |
| deleteWatch(id) | DELETE /v1/watches/:id | void |
| runWatch(id) | POST /v1/watches/:id/run | { runId, watchId, status } |
| listWatchRuns(id, query?) | GET /v1/watches/:id/runs | { items: WatchRun[]; nextCursor } |
| testWatch(options) | POST /v1/watches/test | WatchTestResult |
Verifying Watch webhooks
When a watched page changes Rendex POSTs an HMAC-signed watch.changed (or watch.recovered / watch.error) event to your webhookUrl. Verify it with the raw request body:
import { verifyWebhook, type RendexWebhookPayload } from "@copperline/rendex";
// rawBody is the unparsed request body string
const ok = await verifyWebhook({
payload: rawBody,
signature: req.headers["x-rendex-signature"],
timestamp: req.headers["x-rendex-timestamp"],
secret: process.env.WEBHOOK_SIGNING_SECRET,
});
if (!ok) return res.status(400).end();
const event = JSON.parse(rawBody) as RendexWebhookPayload;
if (event.event === "watch.changed") {
console.log(event.url, event.diffScore, event.diffOverlayUrl);
}A
watch.changedpayload carries the same change fields as aWatchRun:diffScore,diffPixels,beforeUrl,afterUrl,diffOverlayUrl.
Error Handling
import { Rendex, RendexApiError, RendexNetworkError } from "@copperline/rendex";
const rendex = new Rendex("your-api-key");
try {
await rendex.screenshot({ url: "https://example.com" });
} catch (error) {
if (error instanceof RendexApiError) {
// API returned an error
console.error(error.errorCode); // "RATE_LIMITED", "VALIDATION_ERROR", etc.
console.error(error.statusCode); // 429, 400, etc.
console.error(error.requestId); // For debugging with Rendex support
console.error(error.details); // Validation details (if any)
} else if (error instanceof RendexNetworkError) {
// Network failure (DNS, timeout, connection refused)
console.error("Network error:", error.message);
}
}Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| VALIDATION_ERROR | 400 | Invalid request parameters |
| INVALID_URL | 400 | URL failed SSRF validation |
| TIMEOUT | 408 | Page took too long to load |
| CAPTURE_FAILED | 500 | Browser rendering error |
| RATE_LIMITED | 429 | Rate limit exceeded |
| USAGE_EXCEEDED | 429 | Monthly credit limit reached |
| MISSING_API_KEY | 401 | No API key provided |
| INVALID_API_KEY | 401 | API key verification failed |
License
MIT - Copperline Labs LLC
