Mark AI images, songs and clips for the EU AI Act and California's AI Transparency Act in one call. Each file gets an invisible watermark (TrustMark for images, AudioSeal for sound, Video Seal for frames) and signed, time-stamped C2PA Content Credentials that say it was made with AI.
npm install @kennmarq/sdkTypeScript types included, no dependencies. Runs on Node 20 or later, Bun, Deno and edge runtimes. A free key
comes with 1,000 marks a month and every feature: kennmarq.com. The SDK reads it from
KENNMARQ_API_KEY.
import { writeFile } from "node:fs/promises";
import { Kennmarq } from "@kennmarq/sdk";
const kennmarq = new Kennmarq(); // the key from KENNMARQ_API_KEY
const marked = await kennmarq.mark("render.png", {
provider: "Acme Inc.", // your legal name
appName: "Acme Studio", // your AI system
appVersion: "2.1",
model: "FLUX.1 schnell",
markets: ["eu", "us-ca"], // refused if a market's law needs a field you didn't send
});
await writeFile("render.marked.png", marked.data);
console.log(marked.markId, marked.watermarkId, marked.marksUsed);mark() takes a path, a Blob or File, an ArrayBuffer or a Uint8Array (a Node Buffer too). Files over
4 MB go through an upload by themselves: pictures up to 25 MB, clips up to 1 GB.
// app/api/generate/route.ts
import { generateImage } from "ai";
import { Kennmarq } from "@kennmarq/sdk";
const kennmarq = new Kennmarq();
export async function POST(request: Request) {
const { prompt } = await request.json();
const { image } = await generateImage({ model: "bfl/flux-3-image", prompt });
const marked = await kennmarq.mark(image.uint8Array, { appName: "Acme Studio", markets: ["eu", "us-ca"] });
return new Response(marked.data, { headers: { "Content-Type": marked.contentType } });
}Keep the key on the server: never in the browser. Or let a middleware do it:
@kennmarq/ai-sdk marks whatever generateImage, generateSpeech
and experimental_generateVideo return.
const song = await kennmarq.mark("track.mp3"); // five marks per started minute
const clip = await kennmarq.mark("clip.mp4", { check: "ai-act" }); // a job: waits, then downloads
console.log(clip.watermarkId, clip.marksCounted, clip.audioWatermark);
const job = await kennmarq.mark("long.mp4", { wait: false }); // or start it now…
const done = await kennmarq.waitFor(job.markId!); // …and fetch it laterawait kennmarq.mark("portrait.jpg", { source: "edited", deepfake: true, markets: ["eu"] });
await kennmarq.mark("render.png", { label: true, labelLanguage: "ko", labelPosition: "bottom-left" });label: true draws the EU's visible label ("AI GENERATED", or "AI MODIFIED" for edits) into an image or every
frame of a clip, before the watermark. deepfake: true turns it on, and India's market always does
(docs).
const answer = await kennmarq.check("render.png", "ai-act"); // the safety check: two marks
const report = await kennmarq.verify(marked.data); // free; an image up to 4 MB needs no key
const receipt = await kennmarq.receipt(marked.markId!); // signed, for marks made with markets
const usage = await kennmarq.usage();API errors throw KennmarqError with the API's code, the HTTP status and the requestId
(docs). A safety block throws ContentBlocked, with the check's answer in
safety; nothing is marked or counted. A missing key throws MissingApiKey. A 429 asking to wait a moment is
waited out.
import { ContentBlocked, KennmarqError } from "@kennmarq/sdk";
try {
await kennmarq.mark(file, { check: "ai-act" });
} catch (e) {
if (e instanceof ContentBlocked) console.log("blocked:", e.safety.reason);
else if (e instanceof KennmarqError) console.log(e.code, e.requestId);
else throw e;
}The full API: kennmarq.com/docs. MIT-licensed.