Next.js
Marca ogni file IA creato dalla tua app Next.js con un solo helper lato server. La chiave resta sul server, file grandi e video sono gestiti, e un file che non si riesce a marcare non viene mai servito.
In breve
- Dove: un route handler o una server action, mai codice client.
- Cosa: un solo helper,
lib/kennmarq.ts, per immagini, brani e video. - Campi: la tua azienda, la tua app e i mercati in cui operi: i cinque elementi della California, e una ricevuta per ogni file.
- Se non riesce: la richiesta non va a buon fine, e l’originale non marcato non viene mai salvato né mostrato.
1. La chiave
Crea una chiave nella dashboard (Guida rapida) e mettila nel tuo ambiente: su Vercel, nelle variabili d’ambiente del progetto; in locale, in .env.local.
# .env.local: never NEXT_PUBLIC_, so it never reaches the browser
KENNMARQ_API_KEY=sk_test_…2. L’helper
Una sola funzione, mark(), per immagini, brani e video. I file oltre i 4 MB vengono prima caricati con un upload, e un video viene marcato come job, di cui la funzione attende la fine. Non richiede altro che fetch.
// lib/kennmarq.ts: server code only (npm install server-only)
import "server-only";
const API = "https://api.kennmarq.com/v1";
const auth = { Authorization: `Bearer ${process.env.KENNMARQ_API_KEY}` };
// Your company and your AI system: with them, every mark carries California's five items.
const fields: Record<string, string> = {
source: "created", // or "edited", "composite"
provider: "Acme Inc.",
app_name: "Acme Studio",
app_version: "2.1",
markets: "eu,us-ca",
};
async function failed(res: Response): Promise<never> {
const { error } = await res.json();
throw new Error(`Kennmarq.com ${res.status} ${error.code}: ${error.message}`);
}
/** The marked copy of an image, song or video. Throws rather than hand back the original. */
export async function mark(file: Blob, filename: string, extra: Record<string, string> = {}): Promise<Blob> {
const form = new FormData();
for (const [k, v] of Object.entries({ ...fields, ...extra })) form.append(k, v);
// A request carries at most 4 MB: a bigger file goes up first.
if (file.size < 3_900_000) form.append("file", file, filename);
else form.append("upload", await upload(file));
const res = await fetch(`${API}/mark`, { method: "POST", headers: auth, body: form });
if (res.status === 202) return finished(await res.json()); // a video: marked as a job
if (!res.ok) return failed(res);
return res.blob();
}
async function upload(file: Blob): Promise<string> {
const res = await fetch(`${API}/uploads`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify({ bytes: file.size }),
});
if (!res.ok) return failed(res);
const { id, url } = await res.json();
// No key here: the URL is the permission. fetch sends the Content-Length it's signed for.
const put = await fetch(url, { method: "PUT", body: file });
if (!put.ok) throw new Error(`Kennmarq.com upload: ${put.status}`);
return id;
}
async function finished(job: { url: string }): Promise<Blob> {
for (;;) {
await new Promise((r) => setTimeout(r, 5_000));
const res = await fetch(job.url, { headers: auth });
if (!res.ok) return failed(res);
const m = await res.json();
if (m.status === "failed") throw new Error(`Kennmarq.com: ${m.error?.code}: ${m.error?.message}`);
if (m.status === "completed") return (await fetch(m.download.url)).blob();
}
}Preferisci non mantenere un helper? L’SDK JavaScript fa lo stesso con una sola chiamata, con i tipi: npm install @kennmarq/sdk.
3. Marca ciò che crea il tuo modello
In un route handler, subito dopo la generazione:
// app/api/generate/route.ts
import { mark } from "@/lib/kennmarq";
import { generate } from "@/lib/model"; // your model: Replicate, fal, OpenAI, your own ComfyUI…
// Marking takes a second or two; leave room for a video, or for a slow first call.
export const maxDuration = 60;
export async function POST(request: Request) {
const { prompt } = await request.json();
const image: Blob = await generate(prompt);
const marked = await mark(image, "image.png", { model: "FLUX.1 schnell" });
// Store or return the marked copy only. If mark() threw, this request fails instead.
return new Response(marked, { headers: { "Content-Type": marked.type || "image/png" } });
}Oppure in una server action che salva il risultato:
// app/actions.ts
"use server";
import { put } from "@/lib/storage"; // S3, R2, Vercel Blob…
import { mark } from "@/lib/kennmarq";
import { generate } from "@/lib/model";
export async function createImage(prompt: string) {
const marked = await mark(await generate(prompt), "image.png");
return put(`images/${crypto.randomUUID()}.png`, marked); // never the unmarked original
}Video
Un video viene marcato come job: la chiamata risponde 202, e l’helper chiede lo stato ogni cinque secondi finché non è pronto, di solito un minuto o due. In produzione, fatti avvisare invece da un webhook (mark.completed), e mantieni breve la richiesta.
Quando una chiamata non riesce
429 rate_limited: troppe chiamate in questo minuto per il tuo piano; attendi i secondi indicati daRetry-After.402 quota_exceeded: le marcature di questo mese sono esaurite; passa a un piano superiore o attendi il primo del mese.400 invalid_request: il suo messaggio indica cosa manca;markets=us-carichiedeprovider,app_nameeapp_version.
Tutti i codici sono nella pagina degli errori. Per vedere il risultato, trascina un’immagine marcata sul verificatore. Usi il Vercel AI SDK? La sua guida usa lo stesso helper.