kamy — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited kamy (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Kamy renders pixel-perfect PDFs from HTML/Handlebars templates via a hosted Chromium pipeline, and sends those PDFs for legally-binding e-signature with a built-in audit trail. This skill teaches you when and how to call the Kamy API on the user's behalf.
Activate whenever the user asks to:
agreement, certificate, report, payslip, brochure, flyer, or any printable document.
triggered receipts, etc.).
consent forms, HR paperwork, vendor agreements. Kamy issues a sign link the recipient opens in their browser, captures the signature with an audit trail, and seals the result with PAdES-grade cryptography.
/verify/<sha256> is the canonical "is thisdocument genuinely Kamy-sealed?" answer.
Do not activate for:
tool).
The user needs a Kamy API key from https://kamy.dev/dashboard/keys. Read it from process.env.KAMY_API_KEY. If it's not set, ask the user to provide one before running any code.
pnpm add @kamydev/sdkUse a stored template. Templates live in the user's Kamy dashboard and accept JSON data:
import { Kamy } from "@kamydev/sdk";
const kamy = new Kamy({ apiKey: process.env.KAMY_API_KEY! });
const { id, url, bytes, warnings } = await kamy.render({
template: "invoice",
data: {
invoice: { number: "INV-2026-0142", issuedAt: "2026-04-23" },
customer: { name: "Acme Corp", email: "[email protected]" },
lineItems: [
{ description: "Consulting (April)", qty: 40, unitPrice: 150 },
],
totals: { subtotal: 6000, tax: 600, total: 6600 },
},
options: { format: "a4", inlineImages: true },
});
console.log("PDF ready:", url);
for (const w of warnings ?? []) {
console.warn(`[${w.code}] ${w.message}`);
}When the document layout is generated at request time:
const html = `
<html><body>
<h1>Hello ${name}</h1>
<p>Thanks for your order.</p>
</body></html>
`;
const { url } = await kamy.render({
html,
options: { format: "letter", inlineImages: true },
});Snapshot a public URL:
const { url } = await kamy.render({
url: "https://example.com/printable-receipt/123",
options: { format: "a4" },
});| Option | When to set |
|---|---|
format | a4 for international, letter for US documents |
orientation | landscape for spreadsheets / wide tables |
margin | tighten for invoices, loosen for letters |
header / footer | repeat on every page (logo, page numbers) |
pageNumbers: true | quick footer with Page X of Y |
inlineImages: true | recommended for any template with remote <img> |
compressImages: true | recommended for image-heavy brochures |
theme | brand colors / fonts override |
Idempotency-Key so retries don'tproduce duplicate PDFs.
id, not thesigned url — URLs expire after ~1 hour. Refresh with kamy.renders.get(id).
dashboard, then reference them with {{brand.*}} in templates.
Once a PDF is rendered, you can send it for signature in one call. The API returns a sign_url you can email yourself or hand back to the user; Kamy emails the signer automatically when the project has Resend configured.
const render = await kamy.render({ template: "contract", data: { /* ... */ } });
const sig = await kamy.signatures.create({
renderId: render.id,
signerEmail: "[email protected]",
signerName: "Alice Doe",
message: "Please review and sign by Friday.",
});
console.log(sig.sign_url);
// → https://kamy.dev/sign/<token>For higher-value transactions, gate the sign page behind a 6-digit OTP. The signer enters the code before the document loads.
authMethod | Required extras | Channel |
|---|---|---|
link (default) | — | URL possession only |
email_otp | — | Code emailed to signerEmail via Resend |
sms_otp | signerPhone (E.164) | Code texted via Twilio |
await kamy.signatures.create({
renderId: render.id,
signerEmail: "[email protected]",
signerName: "Alice Doe",
authMethod: "email_otp", // recommended for high-stakes docs
});Recommend email_otp by default for contracts / NDAs / offer letters — it adds an inbox-control step without requiring Twilio credentials.
Pass position to anchor the signature on a specific page + coordinate. Coordinates are PDF points (72 dpi, origin bottom-left). Width clamped to [40, 800], height to [20, 400]. Omit to default to bottom-right of the last page (~36 pt padding) and let the signer drag the placeholder.
position: { page: 3, x: 360, y: 80, w: 220, h: 64 }ccEmails — up to 10 observers CC'd on the invite + completionnotice.
expiresIn — seconds (3600–2592000); default 30 days.reminderCadenceHours — 24–168; auto-reminder cron resends theinvite every N hours while pending, up to 3 reminders.
signOnEveryPage — stamp the signature on every page of thesource PDF (B2B contract pattern).
requireStamp — require a corporate stamp / seal alongside thesignature (UAE, KSA, JP, KR, IN, CN workflows).
placedFields — up to 100 sender-defined fillable fields stampedonto flat PDFs at sign time (text, textarea, checkbox, date, initials, radio, dropdown).
Fan one render out to up to 100 signers in a single request.
const batch = await kamy.signatures.bulk({
renderId: render.id,
signers: [
{ signerEmail: "[email protected]", signerName: "A. Example" },
{ signerEmail: "[email protected]", signerName: "B. Example" },
],
message: "Hi {{signerName}} — please review.",
position: { page: 1, x: 360, y: 80, w: 220, h: 64 },
});
for (const row of batch.results) {
console.log(row.signerEmail, row.ok ? row.signUrl : row.error);
}kamy.renders.get(<signedRenderId>).https://kamy.dev/verify/<sha256>— anyone with the bytes can recompute the hash and confirm Kamy-issued.
(invite → opened → consent → signed/declined/delegated, with IP / user-agent at signing). Fetch via GET /api/v1/signatures/{id}/certificate with Bearer auth. Both surfaces — /verify (cryptographic) and the certificate (process) — are what compliance teams ask for.
request still produces a certificate so the audit trail isn't lost.
await kamy.signatures.remind(id); // one per hour per request
await kamy.signatures.void(id); // only valid while pending
await kamy.signatures.get(id); // single
await kamy.signatures.list({ limit: 50 });render() and signatures.* reject with a structured error containing code, message, and optional details. Surface the message to the user verbatim — Kamy errors are written for end-user consumption. The common ones are documented in the kamy-sdk rule.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.