Vai al contenuto

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
# .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
// 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
// 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
// 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 da Retry-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-ca richiede provider, app_name e app_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.