OGuardAI
Integrations

TypeScript SDK

Typed client for the OGuardAI API with automatic session management, error handling, and full TypeScript type exports

The @oguardai/sdk package provides a typed client for the OGuardAI API with automatic session management.

Install

npm install @oguardai/sdk
# or
pnpm add @oguardai/sdk

Basic Usage

Transform and Rehydrate

import { OGuardAIClient } from "@oguardai/sdk";

const client = new OGuardAIClient({
  baseUrl: "http://localhost:3000",
  apiKey: "your-api-key", // optional
});

// Transform: replace PII with semantic tokens
const result = await client.transform({
  input: "Contact Julia Schneider at julia@example.com",
  policy: "default",
});

console.log(result.safe_text);
// "Contact `{{person:p_001:083e771529f3}}` at `{{email:e_001:7b1068662ab5}}`"

// Send safe_text to your LLM
const llmResponse = await callYourLLM(result.safe_text, result.entity_context);

// Rehydrate: restore real values in LLM output
const restored = await client.rehydrate({
  output: llmResponse,
  session_state: result.session_state,
  output_channel: "customer_email",
  restore_mode: "formatted",
});

console.log(restored.restored_text);
// "Dear Frau Julia Schneider, ..."

Detect Only

const detection = await client.detect({
  input: "Email: test@example.com, SSN: 123-45-6789",
  threshold: 0.5,
});

for (const entity of detection.entities) {
  console.log(`${entity.type}: ${entity.value} (confidence: ${entity.confidence})`);
}

Under a production auth mode entity.value is an empty string by default: raw values are returned only when you pass include_values: true AND your API key has the detect_values scope. Types, spans, and confidence are always present. This keeps raw PII out of a detect response unless a caller is explicitly authorized to receive it.

Health and Capabilities

const health = await client.health();
console.log(health.status); // "healthy"

const caps = await client.capabilities();
console.log(caps.entity_types.map(e => e.name));
// ["person", "email", "phone", "iban", "ssn", ...]

Session Management

For multi-turn conversations, use OGuardAISession to automatically carry session state between calls:

import { OGuardAIClient, OGuardAISession } from "@oguardai/sdk";

const client = new OGuardAIClient({ baseUrl: "http://localhost:3000" });
const session = new OGuardAISession(client, "german-support");

// Turn 1
const turn1 = await session.transform("Ich bin Anna Mueller, Kundennummer 948221");
console.log(turn1.safe_text);
// "Ich bin `{{person:p_001:4c19d2a08b5e}}`, Kundennummer `{{customer_id:cid_001:9f27c31d64ab}}`"

const llmReply1 = "Guten Tag `{{person:p_001:4c19d2a08b5e}}`, wie kann ich helfen?";
const restored1 = await session.rehydrate(llmReply1, "customer_email");

// Turn 2: same session, entities are deduplicated
const turn2 = await session.transform("Meine E-Mail ist anna@example.com");
// Anna Mueller is still `{{person:p_001:4c19d2a08b5e}}`

withSession Helper

import { OGuardAIClient, withSession } from "@oguardai/sdk";

const client = new OGuardAIClient({ baseUrl: "http://localhost:3000" });

await withSession(client, "default", async (session) => {
  const result = await session.transform("Hello, I am John Smith");
  // ... call LLM ...
  const restored = await session.rehydrate(llmOutput, "user_output");
});

Chat Messages

guardedChat tokenizes a full chat array in one call. The server scans every message field by field, tokenizing each string-bearing field (content, multimodal text parts, tool-call arguments) and returns safeMessages with the same shape. Token ids are shared across messages and one sealed session covers the whole conversation. Rehydrate the model's reply with rehydrateResponse:

const { safeMessages, sessionState } = await client.guardedChat(
  [
    { role: "system", content: "You are a support agent." },
    { role: "user", content: "I am Julia Schneider, julia@example.com" },
  ],
  { policy: "german-support" },
);

const reply = await callYourLLM(safeMessages); // sees tokens only
const restored = await client.rehydrateResponse(reply, sessionState, "customer_email");

guardedCall wraps any string -> string step (a LangChain chain, a raw provider call) so it transforms the input, runs your function on the tokenized text, and rehydrates the output. It fails closed if transform returns no session state:

const answer = await client.guardedCall(
  (safeText) => callYourLLM(safeText),
  "Draft a reply to Julia Schneider",
  { policy: "default", outputChannel: "customer_email" },
);

Batch

Transform or detect many inputs in one call. Each item takes an optional per-item policy and language, so a single batch can mix languages. session_mode is independent (default) or shared.

const batch = await client.batchTransform({
  items: [
    { text: "Ich bin Anna Mueller", language: "de" },
    { text: "Contact john@example.com", language: "en" },
  ],
  session_mode: "independent",
});

for (const item of batch.results) {
  console.log(item.index, item.safe_text, item.error);
}

const detected = await client.batchDetect({
  items: [{ text: "SSN 123-45-6789", policy: "strict-pii" }],
});

RAG

The RAG methods carry a corpus_id for cross-document and cross-session token identity: passing the same corpus_id on ingest, query, and context makes the same raw value tokenize to the same token id deterministically across documents and sessions, which is what lets a query entity align with an ingested document entity. The scope is that one corpus, not a global fingerprint.

// 1. Ingest: chunk + tokenize a document, store safe chunks in your vector DB
const ingest = await client.ragIngest({
  text: documentText,
  chunking_strategy: "paragraph",
  corpus_id: "support-kb",
});
// Keep each chunk's chunk_session_state for step 3
const docSessions = ingest.chunks.map((c) => c.chunk_session_state);

// 2. Query: tokenize the user question for vector search
const query = await client.ragQuery({ query: userQuestion, corpus_id: "support-kb" });
const hits = await vectorSearch(query.safe_query);

// 3. Context: merge retrieved chunks into a safe LLM context
const ctx = await client.ragContext({
  chunks: hits.map((h) => h.text),
  session_state: query.session_state,
  document_sessions: docSessions,
  corpus_id: "support-kb",
});
const llmAnswer = await callYourLLM(ctx.safe_chunks.join("\n"));

// 4. Answer: validate tokens resolve; restore host-side
const answer = await client.ragAnswer({
  answer: llmAnswer,
  session_state: ctx.session_state,
  output_channel: "user_output",
});

// GDPR erasure: forget every value in an ingest session
await client.ragDelete({ session_state: ingest.chunks[0].chunk_session_state });

Files and Images

transformFile ingests a document (PDF, DOCX, TXT), extracts text, and tokenizes detected PII. transformImage runs OCR (Tesseract in the server image) and returns tokenized text plus bounding_boxes for client-side overlay. redactImage returns the redacted image bytes and deliberately over-redacts (it applies no policy) for visual safety; it accepts an optional language hint that is forwarded to OCR and detection.

const doc = await client.transformFile(pdfBytes, "contract.pdf", { policy: "legal-privilege" });
console.log(doc.safe_text);

const img = await client.transformImage(pngBytes, "scan.png", { language: "de" });
console.log(img.safe_text, img.bounding_boxes);

const redacted = await client.redactImage(pngBytes, "scan.png", { language: "de" }); // Uint8Array

The image route resolves the tenant/global default policy, so it accepts only language, not a policy field. Person and company names in a scan depend on the NER sidecar; without it those names are not detected and are not redacted, so treat image redaction as a data-minimization control, not an absolute guarantee.

Streaming

transformStream and rehydrateStream yield chunks over SSE. Transform runs the full pipeline, then streams safe_text; rehydrate streams restored_text:

for await (const chunk of client.transformStream("Contact Julia Schneider", { policy: "default" })) {
  process.stdout.write(chunk);
}

for await (const chunk of client.rehydrateStream(llmOutput, sessionState, { outputChannel: "user_output" })) {
  process.stdout.write(chunk);
}

// Typed stream events carry sessionState on the terminal "complete" event
let capturedState: string | undefined;
for await (const event of client.transformStreamEvents("Contact Julia Schneider", { policy: "default" })) {
  if (event.text) process.stdout.write(event.text);
  if (event.sessionState) capturedState = event.sessionState;
}
// rehydrateStreamEvents() yields the same StreamEvent shape

Revocation and Admin

// Revoke a value so future rehydrates return [DELETED]
await client.revoke("email", "julia@example.com");
await client.bulkRevoke([{ entityType: "email", value: "a@b.com" }]);

// Preview policy decisions without transforming
const decision = await client.evaluatePolicy({
  entities: [{ type: "email", value: "julia@example.com" }],
  policy: "default",
});

// Admin/ops (no PII); scoped server-side
const diag = await client.diagnostics();
const prom = await client.metrics();
const check = await client.validatePolicies({ directory: "/etc/oguardai/policies" });
const count = await client.revocationCount();      // active revocation count
const reloaded = await client.reloadPolicies();    // hot-reload the policy directory (fail-closed)

// Session lifecycle: identify a session by exactly one of session_id or session_state.
// The sealed backend holds no server-side state, so deleteSession reports deleted: false.
const status = await client.sessionStatus({ session_state: result.session_state });
await client.deleteSession({ session_state: result.session_state });

Error Handling

The SDK throws typed errors for different failure modes:

import {
  OGuardAIClient,
  OGuardAIError,
  ValidationError,
  AuthError,
  SessionExpiredError,
  PolicyDeniedError,
  TimeoutError,
  OutputBlockedError,
  TokenRepairError,
  RateLimitError,
} from "@oguardai/sdk";

const client = new OGuardAIClient({ baseUrl: "http://localhost:3000" });

try {
  await client.transform({ input: "some text" });
} catch (error) {
  if (error instanceof ValidationError) {
    // 400: malformed request
    console.error("Invalid request:", error.message);
  } else if (error instanceof AuthError) {
    // 401/403: authentication failed
    console.error("Auth error:", error.message);
  } else if (error instanceof SessionExpiredError) {
    // 410: session TTL elapsed
    console.error("Session expired, create a new one");
  } else if (error instanceof PolicyDeniedError) {
    // 403: only when a redacted entity's rule sets on_redact: reject.
    // Routine redaction succeeds with the value redacted in safe_text.
    console.error("Policy rejected the request:", error.message);
  } else if (error instanceof OutputBlockedError) {
    // 422: the output guard blocked newly generated PII in the model output
    console.error("Output blocked:", error.message);
  } else if (error instanceof TokenRepairError) {
    // 422: a malformed {{type:id:cap}} token could not be repaired for restoration
    console.error("Token repair failed:", error.message);
  } else if (error instanceof RateLimitError) {
    // 429: rate limit exceeded; error.retryAfter carries the Retry-After header
    console.error("Rate limited, retry after:", error.retryAfter);
  } else if (error instanceof TimeoutError) {
    // Request timed out
    console.error("Request timed out");
  } else if (error instanceof OGuardAIError) {
    // Other API error
    console.error(`Error [${error.code}]: ${error.message}`);
  }
}

Configuration

const client = new OGuardAIClient({
  baseUrl: "http://localhost:3000",  // Required
  apiKey: "your-api-key",           // Optional: sent as X-API-Key header
  timeout: 30000,                   // Optional: request timeout in ms (default: 30000)
});

Types

All request/response types are exported:

import type {
  TransformRequest,
  TransformResponse,
  RehydrateRequest,
  RehydrateResponse,
  DetectRequest,
  DetectResponse,
  HealthResponse,
  CapabilitiesResponse,
  EntityContext,
  EntityInfo,
  RestoreMode,
  OutputChannel,
  BatchTransformRequest,
  BatchTransformResponse,
  BatchDetectRequest,
  BatchDetectResponse,
  RagIngestRequest,
  RagIngestResponse,
  RagQueryRequest,
  RagQueryResponse,
  RagContextRequest,
  RagContextResponse,
  RagAnswerRequest,
  RagAnswerResponse,
  RagDeleteRequest,
  RagDeleteResponse,
  FileTransformResponse,
  ImageTransformResponse,
  BoundingBox,
  RevokeResponse,
  EvaluatePolicyRequest,
  EvaluatePolicyResponse,
  DiagnosticsResponse,
  ValidatePoliciesRequest,
  ValidatePoliciesResponse,
} from "@oguardai/sdk";