Kamy
Kamy renders invoices, receipts, contracts, and 5 more production-grade templates with a single REST call or TypeScript SDK method. No headless browser. No DevOps.
01
Install the SDK
The TypeScript SDK works in Node.js, Deno, Bun, and any modern server runtime.
bash
npm install @kamydev/sdk
# or
pnpm add @kamydev/sdk
# or
yarn add @kamydev/sdk
02
Get an API key
Sign in to the dashboard, open API Keys, click New key. Copy the key once — it's shown only on creation.
Environment variable
KAMY_API_KEY=kamy_pk_...
Keep the key on your server only. Never ship it in client-side code.
03
Quick start
Render an invoice in three lines of TypeScript.
ts
import Kamy from "@kamydev/sdk";
const kamy = new Kamy({ apiKey: process.env.KAMY_API_KEY! });
const pdf = await kamy.render({
template: "invoice",
data: {
invoiceNumber: "INV-001",
issueDate: "2026-01-01",
dueDate: "2026-01-31",
from: { name: "Acme Corp", address: ["123 Main St", "SF, CA"] },
to: { name: "Client Inc", address: ["456 Oak Ave", "NY, NY"] },
lineItems: [
{ description: "Consulting", quantity: 10, unitPrice: 150, amount: 1500 },
],
subtotal: 1500,
total: 1500,
currency: "USD",
},
});
console.log(pdf.url); // signed URL, valid 1 hour
04
Using from a Node backend
Inside an API route (Next.js, Hono, Express, Fastify, Nest) — render on demand and return the URL.
ts
// app/api/invoice/route.ts (Next.js)
import Kamy from "@kamydev/sdk";
export async function POST(req: Request) {
const body = await req.json();
const pdf = await kamy.render({ template: "invoice", data: body });
return Response.json({ url: pdf.url });
}
05
Using from the frontend
Important: API keys must never be embedded in frontend code. Call your own backend, which calls Kamy. The browser only sees the resulting signed URL.
ts
// client-side React
async function downloadInvoice(data: InvoiceData) {
const res = await fetch("/api/invoice", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
const { url } = await res.json();
window.open(url, "_blank");
}
06
Raw REST API
If you're not in a JS environment, call the REST endpoint directly.
bash
curl -X POST https://kamy.dev/api/v1/render \
-H "Authorization: Bearer $KAMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "invoice",
"data": { "invoiceNumber": "INV-001", "total": 1500, "currency": "USD" }
}'
Response: { id, url, bytes, durationMs, templateId, createdAt }
Prefer Postman, Insomnia, or Bruno? Import the official collection — postman/kamy.json — covers all 23 v1 routes with sample bodies, path-variable defaults, and Bearer auth pre-wired to a single {{apiKey}} collection variable.
07
Built-in templates
Reference a template by slug. Each has a fully typed data schema (see InvoiceData, ReceiptData, etc. exported from the SDK).
| Slug | Name | Description |
|---|---|---|
| invoice | Invoice | Line items, tax, discounts, payment terms. |
| receipt | Receipt | Compact thermal-style, monospace amounts. |
| quote | Quote | Validity date, quote number, line items. |
| contract | Contract | Numbered sections, signature blocks. |
| shipping-label | Shipping Label | 4×6 inch with barcode and tracking. |
| certificate | Certificate | Decorative border, signature line. |
| report | Report | Cover, TOC, auto headers/footers. |
| agreement | Agreement | One-page with parties and terms. |
| uae-tax-invoice | UAE Tax Invoice | FTA-compliant bilingual AR/EN, 5% VAT. |
| ksa-zatca-invoice | KSA ZATCA Invoice | ZATCA Phase-1 simplified, TLV QR. |
08
Custom Handlebars templates
Upload your own HTML/Handlebars templates from the Templates page — or push them straight from CI with the kamy push CLI so your production templates stay in lockstep with your repo on every commit.
bash
# CI: idempotent upsert keyed on slug, safe to re-run
npm i -g @kamydev/cli
export KAMY_API_KEY=kamy_pk_...
kamy push templates/invoice.hbs --css templates/invoice.css --tag finance
ts
// Or from the SDK
await kamy.pushTemplate({
slug: "invoice-acme", // creates if missing, updates if present
name: "Acme Invoice",
html: await fs.readFile("invoice.hbs", "utf8"),
css: await fs.readFile("invoice.css", "utf8"),
});
// Then render by slug just like a built-in
await kamy.render({
template: "invoice-acme",
data: { orderId: "123", items: [/* … */] },
});
Patching an existing template? updateTemplate() accepts either a UUID or a slug as its first argument — no need to GET-then-PATCH-by-id. Same for deleteTemplate().
ts
// Slug-keyed PATCH — single round trip
await kamy.updateTemplate("invoice-acme", {
name: "Acme Invoice (Q2 redesign)",
html: updatedHbs,
});
// Slug-keyed DELETE
await kamy.deleteTemplate("invoice-acme");
Handlebars helpers available: currency, date, add, number, plus all built-ins (each, if, unless). Validate payloads against your schema in CI without burning credits by passing options.validateOnly: true.
09
Asset uploads (large images, fonts, logos)
Render requests are capped at 6 MB of JSON body. Anything larger (high-resolution photos, multi-page brochure imagery, bundled font files) should be uploaded once via createUpload() and then referenced from your template by URL — same render call, fraction of the bytes on the wire.
Uploads use a two-step pattern: ask Kamy for a pre-signed PUT URL, stream the file body straight to storage, then embed the returned publicUrl — or the shorthand kamy://asset/<id> URI — anywhere in your render data. The render route resolves kamy:// URIs to fresh signed URLs automatically, so you never have to manage signed-URL expiry yourself.
ts
import { readFile } from "node:fs/promises";
// 1. Ask Kamy for a pre-signed PUT URL (15-min single-use).
const upload = await kamy.createUpload({
filename: "hero.jpg",
contentType: "image/jpeg",
sizeBytes: 4_200_000, // optional pre-flight check vs 100 MB cap
});
// 2. Stream the file body to Supabase Storage with PUT (NOT POST).
await fetch(upload.uploadUrl, {
method: "PUT",
headers: { "Content-Type": "image/jpeg" },
body: await readFile("./hero.jpg"),
});
// 3a. Reference the long-lived publicUrl directly in your data…
await kamy.render({
template: "flyer",
data: { heroImage: upload.publicUrl },
});
// 3b. …or use the kamy:// shorthand. The render route auto-resolves
// it to a freshly-signed URL on every render, so no expiry to manage.
await kamy.render({
template: "flyer",
data: { heroImage: `kamy://asset/${upload.path.split("/").pop()}` },
});
Hard cap is 100 MB per object. If a render request still hits the 6 MB limit after switching to uploads, you'll get a structured 413 PAYLOAD_TOO_LARGE with the exact limit and remediation hint in the error body.
10
Async, batch & merge
For long-running renders, fire-and-forget jobs, and bulk pipelines.
ts
// Async — enqueue and poll
const job = await kamy.renderAsync({ template: "report", data });
const pdf = await job.wait({ pollIntervalMs: 1000, timeoutMs: 120_000 });
// Batch — up to 100 renders in one request
const { results } = await kamy.renderBatch([
{ template: "invoice", data: { invoiceNumber: "INV-001" /* … */ } },
{ template: "receipt", data: { receiptNumber: "REC-002" /* … */ } },
]);
// Merge — combine 2–20 rendered PDFs into one document
const merged = await kamy.merge([pdf1.id, pdf2.id, pdf3.id]);
// Idempotency — safe to retry without double-charging
await kamy.render({
template: "invoice",
data,
idempotencyKey: "order-12345", // any unique string up to 64 chars
});
// Download helpers
await pdf.toFile("./invoice.pdf"); // write to disk
const buf = await pdf.toBuffer();
const stream = await pdf.toStream();
11
Webhooks
Subscribe to render.completed, render.failed, and render.started events. Each delivery is signed with HMAC-SHA256 — verify with verifyWebhook before processing.
ts
// 1. Create a subscription
const hook = await kamy.webhooks.create({
url: "https://example.com/hooks/kamy",
events: ["render.completed", "render.failed"],
});
// Save hook.secret — it is shown only once.
// 2. Verify deliveries in your handler
import { verifyWebhook } from "@kamydev/sdk";
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get("x-kamy-signature") ?? "";
const ok = await verifyWebhook({
body,
signature: sig,
secret: process.env.KAMY_WEBHOOK_SECRET!,
});
if (!ok) return new Response("invalid signature", { status: 401 });
const event = JSON.parse(body);
// event.type, event.data.render, event.data.jobId
return new Response("ok");
}
12
Quota & usage (programmatic)
Two read-only endpoints expose plan limits and live usage so you can wire dashboards, quota meters, or CI pre-flight checks without scraping invoice emails or guessing when you'll hit the wall.
ts
// Plan + static limits — call once at app boot, cache it.
const me = await kamy.me();
console.log(me.plan); // "free" | "pro" | "team"
console.log(me.limits.rendersPerMonth); // number, or null for unlimited
console.log(me.limits.customTemplates); // boolean
console.log(me.limits.seats); // number
// Live UTC-calendar-month usage — cheap, safe to poll.
const usage = await kamy.usage();
console.log(usage.renders.used); // 1_247
console.log(usage.renders.quota); // 10_000 (Pro plan; null on unlimited tiers)
console.log(usage.renders.remaining); // 8_753 (null on unlimited tiers)
console.log(usage.period.start, usage.period.end);
// Pre-flight before a bulk job
if (usage.renders.remaining !== null && usage.renders.remaining < jobs.length) {
throw new Error(`Need ${jobs.length} renders, only ${usage.renders.remaining} left.`);
}
Both endpoints are quota-free — they don't count against your monthly render budget. me() changes only on plan upgrades, so cache it; usage() is the one to poll.
13
Error handling
ts
import Kamy, { KamyError } from "@kamydev/sdk";
try {
const pdf = await kamy.render({ template: "invoice", data });
} catch (err) {
if (err instanceof KamyError) {
console.error(err.code); // e.g. "QUOTA_EXCEEDED"
console.error(err.status); // HTTP status
console.error(err.message); // Human-readable
}
}
| Code | HTTP | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid API key |
| INVALID_API_KEY | 401 | Key format invalid |
| API_KEY_REVOKED | 401 | Key was revoked |
| FORBIDDEN | 403 | Access denied |
| NOT_FOUND | 404 | Template or render not found |
| VALIDATION_ERROR | 422 | Data doesn't match template schema |
| RATE_LIMITED | 429 | Too many requests |
| QUOTA_EXCEEDED | 402 | Monthly render limit reached |
| PAYLOAD_TOO_LARGE | 413 | Request body exceeded the 6 MB JSON limit |
| RENDER_FAILED | 500 | PDF generation failed |
14
MCP server (Claude, Cursor)
Kamy exposes an MCP server at https://mcp.kamy.dev/mcp so AI agents can render PDFs directly. Add this to your Claude Desktop or Cursor config:
json
{
"mcpServers": {
"kamy": {
"url": "https://mcp.kamy.dev/mcp",
"headers": { "Authorization": "Bearer kamy_pk_..." }
}
}
}
Available tools: render_pdf, list_templates, get_template, list_renders, install_sdk.
Ready to render?
500 free renders every month.
Create a free accountGet your API key
Servidores relacionados
Alpha Vantage MCP Server
patrocinadorAccess financial market data: realtime & historical stock, ETF, options, forex, crypto, commodities, fundamentals, technical indicators, & more
MKP
Model Kontext Protocol Server for Kubernetes that allows LLM-powered applications to interact with Kubernetes clusters through native Go implementation with direct API integration and comprehensive resource management.
AIO-MCP Server
An MCP server with integrations for GitLab, Jira, Confluence, and YouTube, providing AI-powered search and development utility tools.
Vibetest Use
Automated QA testing for websites to find UI bugs, broken links, and accessibility issues.
Kafka MCP
A natural language interface to manage Apache Kafka operations.
BlenderMCP
Connects Blender to Claude AI via the Model Context Protocol (MCP), enabling direct interaction and control for prompt-assisted 3D modeling, scene creation, and manipulation.
WordPress MCP
A Python MCP server for interacting with a local WordPress instance.
Reports MCP Server
Manages penetration testing reports and vulnerabilities via a REST API.
Remote MCP Server Chatbot
A demonstration of deploying a remote MCP server on Cloudflare Workers without authentication.
MCP Spring Boot Actuator
Spring Boot Actuator MCP server — analyzes health, metrics, environment, beans, and startup endpoints. Detects configuration issues and security risks with actionable recommendations.
MCP Command Server
A server for securely executing commands on the host system, requiring Java 21 or higher.