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,});Install
Section titled “Install”npm install @tup-ai/cortina# oderpnpm add @tup-ai/cortina# oderyarn add @tup-ai/cortinaVoraussetzungen: Node.js ≥ 18 (für fetch). Zero Runtime-
Dependencies — die SDK nutzt das Standard-fetch, keine axios /
node-fetch / undici-Abhängigkeit.
Quickstart
Section titled “Quickstart”Convenience: client.anonymize(text)
Section titled “Convenience: client.anonymize(text)”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,});Sessions: client.createSession()
Section titled “Sessions: client.createSession()”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].
Batch: client.anonymizeBatch(inputs)
Section titled “Batch: client.anonymizeBatch(inputs)”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.
Stage-Filter
Section titled “Stage-Filter”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"] });Configuration
Section titled “Configuration”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});Retry-with-Backoff
Section titled “Retry-with-Backoff”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).
| Error | Retried? |
|---|---|
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 });Cancellation
Section titled “Cancellation”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.
Error-Handling
Section titled “Error-Handling”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.
Public Response-Shape
Section titled “Public Response-Shape”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.
Kompatibilität
Section titled “Kompatibilität”- Node.js ≥ 18 (Built-in
fetch) - Deno, Bun: über Node-Compat-Layer
- Cloudflare Workers / Vercel Edge: eigenes
fetchüber diefetch- Option injizieren