Skip to content

TypeScript SDK

@tup-ai/cortina ist der offizielle TypeScript-Client für Cortina. Zero Runtime-Dependencies, dual ESM + CJS-Build, vollständige TypeScript-Typen, Built-in Retry-with-Backoff, AbortSignal-Support.

import { CortinaClient } from "@tup-ai/cortina";
const cortina = new CortinaClient({ apiKey: process.env.CORTINA_API_KEY! });
const masked = await cortina.anonymize(
"Hans Müller, IBAN DE89 3704 0044 0532 0130 00, ruft +49 89 1234567 an.",
);
// masked.anonymizedText:
// "[PERSON_1], IBAN [DE_IBAN_1], ruft [DE_PHONE_NUMBER_1] an."
const llmReply = await callMyLLM(masked.anonymizedText);
const restored = await cortina.deanonymize({
text: llmReply,
sessionId: masked.sessionId,
});
Terminal window
npm install @tup-ai/cortina
# oder
pnpm add @tup-ai/cortina
# oder
yarn add @tup-ai/cortina

Voraussetzungen: Node.js ≥ 18 (für fetch). Zero Runtime- Dependencies — die SDK nutzt das Standard-fetch, keine axios / node-fetch / undici-Abhängigkeit.

Der kürzeste Pfad. Die SDK erstellt automatisch eine Session und gibt ihre ID auf der Response mit zurück.

const cortina = new CortinaClient({ apiKey });
const masked = await cortina.anonymize("Anna Müller wohnt in Köln.");
console.log(masked.anonymizedText); // "[PERSON_1] wohnt in [LOCATION_1]."
// Round-Trip später, im selben oder einem anderen Prozess:
const restored = await cortina.deanonymize({
text: masked.anonymizedText,
sessionId: masked.sessionId,
});

Wenn du mehrere Texte mit gemeinsamem Pseudonym-Vokabular anonymisieren willst (Multi-Turn-Conversation, Dokument in Chunks), nutze eine explizite Session. Innerhalb derselben Session bekommt „Anna” jedes Mal denselben [PERSON_1]-Platzhalter.

const session = await cortina.createSession({ ttlSeconds: 3600 });
try {
const turn1 = await session.anonymize("Hans rief seine Frau Anna an.");
const turn2 = await session.anonymize("Anna ist Ärztin in Frankfurt.");
// Über turn1 und turn2 hinweg ist „Anna" derselbe [PERSON_2].
const llmReply = await callMyLLM(
[turn1, turn2].map((t) => t.anonymizedText).join("\n"),
);
const restored = await session.deanonymize(llmReply);
} finally {
await session.close(); // Redis-Mapping sofort löschen
}

ttlSeconds defaults zu 3600 (1 h), Server-Bounds [60, 86400].

Parallele Anonymisierung mit beschränkter Concurrency. Per-Element- Ergebnisse kommen in der Input-Reihenfolge zurück.

const batch = await cortina.anonymizeBatch(
[
"Anna lebt in Köln.",
"Hans Müller, IBAN DE89 3704 0044 0532 0130 00.",
"Ruf mich an unter +49 89 1234567",
],
{ concurrency: 5 },
);
console.log(batch.successCount, batch.failureCount);
for (const r of batch.results) {
if (r.status === "fulfilled") {
console.log(r.index, r.value.anonymizedText);
} else {
console.warn(r.index, r.reason);
}
}

Wenn ein Input keine sessionId mitbringt, erstellt der Batch eine gemeinsame Session vorab und nutzt sie für alle solchen Inputs — ein 100-Element-Batch kostet so eine Session-Create-Round-Trip, nicht 100.

stopOnFirstError: true schaltet auf Fail-Fast: der erste Fehler cancelt alle ausstehenden Tasks und der Call wirft dann.

Cortina läuft per Default mit allen vier Stages. Wenn die LLM-Latenz nicht zum Workflow passt, einzelne Stages skippen:

await cortina.anonymize(text, { stages: ["stage1", "stage2"] });

Pipeline-Trade-offs.

new CortinaClient({
apiKey: string; // required, "tup_..."
baseUrl?: string; // default "https://api.tup-ai.de/v1/cortina"
timeout?: number; // ms, default 30_000
fetch?: typeof fetch; // Inject custom fetch (Edge-Runtimes, Tests)
defaultHeaders?: Record<string, string>; // Merged in jeden Request
retry?: Partial<RetryOptions> | false; // siehe unten
});

Transiente Transport-Fehler werden automatisch retried. Defaults: 3 Retries, 500 ms Base-Delay, 30 s Cap, ±100 ms Jitter. Der Retry-After- Header bei 429 wird respektiert (gecappt auf maxDelay).

ErrorRetried?
RateLimitError (429)
ServerError 502 / 503 / 504
ServerError andere 5xx
NetworkError
TimeoutError
AuthenticationError (401 / 403)
ValidationError (400 / 422)
NotFoundError (404)
Externe AbortSignal-Cancel

Override oder ausschalten:

new CortinaClient({
apiKey,
retry: { maxRetries: 5, baseDelay: 1_000, maxDelay: 60_000, jitter: 200 },
});
// Komplett aus — genau ein Versuch:
new CortinaClient({ apiKey, retry: false });

Jeder Call akzeptiert ein externes AbortSignal. Cancellation propagiert durch die Retry-Loop — feuert das Signal mid-sleep, exit-t die Loop mit AbortError ohne Re-Try.

const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);
try {
await cortina.anonymize(text, { signal: controller.signal });
} catch (err) {
if (err instanceof Error && err.name === "AbortError") {
// user cancelled
}
}

Zusätzlich gibt es einen Built-in Per-Call-Timeout (default 30 s, konfigurierbar via Constructor) der als TimeoutError surfaced.

Alle SDK-Errors erben von CortinaError. Die Klassen-Hierarchie spiegelt das HTTP-Status-Set des Service:

import {
CortinaClient,
CortinaError,
AuthenticationError, // 401 / 403
ValidationError, // 400 / 422
NotFoundError, // 404
RateLimitError, // 429 — hat `retryAfter` (Sekunden)
ServerError, // 5xx
NetworkError, // fetch reject (DNS, Connection-Refused, TLS)
TimeoutError, // Per-Call-Timeout überschritten
} from "@tup-ai/cortina";
try {
await cortina.anonymize(text);
} catch (err) {
if (err instanceof RateLimitError) {
await new Promise((r) => setTimeout(r, (err.retryAfter ?? 1) * 1000));
} else if (err instanceof AuthenticationError) {
// Key rotieren, Ops alerten
} else if (err instanceof CortinaError) {
console.error(err.status, err.details?.code, err.message);
} else {
throw err;
}
}

err.details carriert das machine-readable { code, message } der Service-Response — Caller können auf stable Codes wie "rate_limit_exceeded", "session_expired", "validation_error", "session_not_found" branchen, ohne Strings zu parsen.

Wire ist snake_case (FastAPI / Pydantic), die SDK-Typen sind camelCase. Die Transform-Layer in der SDK hält die Konvertierung am Boundary.

interface AnonymizeResponse {
anonymizedText: string;
entities: PiiEntity[];
sessionId: string;
auditFlags: string[];
}
interface PiiEntity {
entityType: string; // "PERSON" | "DE_IBAN" | "EMAIL_ADDRESS" | ...
start: number; // UTF-16 offset im Originaltext
end: number;
placeholder: string; // "[PERSON_1]"
confidence: number; // [0, 1]
}

re_id_score, Adversarial-Iteration-Counts und LLM-Token-Counts werden absichtlich nicht in der Response surfaced — sie leben nur im Audit-Log.

  • Node.js ≥ 18 (Built-in fetch)
  • Deno, Bun: über Node-Compat-Layer
  • Cloudflare Workers / Vercel Edge: eigenes fetch über die fetch- Option injizieren

API-Reference: AnonymizeSessions & Mappings