raipii
v0.1.0
Published
Node.js SDK for the raipii PII detection and sanitization API
Maintainers
Readme
raipii Node.js SDK
Detect and sanitize PII before it reaches your LLM. Replace real data with tokens or realistic fakes. Restore original values after the model responds.
Install
npm install raipii
# or
yarn add raipii
# or
pnpm add raipiiRequires Node 18+. Zero runtime dependencies — uses native fetch.
Quick start
import { Raipii } from "raipii";
const ps = new Raipii({ apiKey: "ps_live_..." });
// 1. Sanitize — strip PII before sending to your LLM
const result = await ps.sanitize(
"Hi, I'm John Smith — [email protected], SSN 392-45-7810",
{ mode: "fake_substitute" },
);
console.log(result.sanitizedText);
// "Hi, I'm Michael Torres — [email protected], SSN 847-23-1956"
// 2. Call your LLM with the sanitized prompt
const llmResponse = await yourLLM(result.sanitizedText);
// 3. Restore — put original values back in the response
const original = await ps.restore(llmResponse, result.sessionId);
console.log(original.restoredText);
// "Hi, I'm John Smith — [email protected], SSN 392-45-7810"Set RAIPII_API_KEY in your environment to avoid passing the key in code:
export RAIPII_API_KEY=ps_live_...const ps = new Raipii(); // reads RAIPII_API_KEY automaticallyGet a free API key (2M chars/month) at raipii.com.
Sanitize modes
token (default)
Replaces PII with labelled placeholder tokens. Safe, lossless, fully reversible.
const result = await ps.sanitize(
"Schedule a call with Jane Doe at [email protected] on 555-867-5309",
{ mode: "token" },
);
console.log(result.sanitizedText);
// "Schedule a call with [PERSON_1] at [EMAIL_1] on [PHONE_1]"
const restored = await ps.restore(llmResponse, result.sessionId);fake_substitute
Replaces PII with realistic values. The LLM sees natural data and produces better output. All substitutions are reversed on restore.
const result = await ps.sanitize(
"Write a summary for John Smith, DOB 1985-03-12, SSN 392-45-7810",
{ mode: "fake_substitute" },
);
console.log(result.sanitizedText);
// "Write a summary for Michael Torres, DOB 1991-07-24, SSN 847-23-1956"
const restored = await ps.restore(llmResponse, result.sessionId);
// LLM response has fake values swapped back to real onesredact
Replaces PII with [REDACTED]. One-way — there is nothing to restore.
const result = await ps.sanitize(
"Patient John Smith, MRN 00123456, DOB 1985-03-12",
{ mode: "redact" },
);
console.log(result.sanitizedText);
// "Patient [REDACTED], MRN [REDACTED], DOB [REDACTED]"Detect only
Scan text for PII without modifying it.
const result = await ps.detect("My SSN is 392-45-7810 and email is [email protected]");
console.log(result.piiDetected); // true
console.log(result.riskLevel); // "HIGH"
for (const entity of result.entitiesFound) {
console.log(`${entity.type}: ${entity.value} (${Math.round(entity.confidence * 100)}%)`);
}
// US_SSN: 392-45-7810 (100%)
// EMAIL: [email protected] (100%)Risk levels: NONE → LOW → MEDIUM → HIGH
| Risk | Triggered by |
|---|---|
| HIGH | SSN, credit card, MRN, bank account, tax ID |
| MEDIUM | Person name, email, date of birth, address |
| LOW | Any other detected entity |
| NONE | No PII found |
Detected entity types
| Type | Example | Tier |
|---|---|---|
| PERSON | John Smith | All tiers |
| EMAIL | [email protected] | All tiers |
| PHONE | 555-867-5309 | All tiers |
| US_SSN | 392-45-7810 | All tiers |
| CREDIT_CARD | 4111 1111 1111 1111 | All tiers |
| DATE_OF_BIRTH | 1985-03-12 | All tiers |
| ADDRESS | 123 Main St, Austin TX | Growth+ |
| IP_ADDRESS | 192.168.1.1 | All tiers |
| MEDICAL_RECORD_NUMBER | MRN 00123456 | All tiers |
| BANK_ACCOUNT | 12345678 | All tiers |
| TAX_ID | 12-3456789 | All tiers |
| IBAN | GB29 NWBK 6016 1331 9268 19 | All tiers |
| JWT | eyJhbGci... | All tiers |
| AWS_KEY | AKIA... | All tiers |
Starter tier detects structured PII reliably. Growth and Business tiers add enhanced contextual detection with higher accuracy for unstructured entities such as names and addresses.
Multi-turn conversations
Keep consistent fake substitutions across all turns of a conversation.
const conv = await ps.conversations.create({ ttl: 86400 }); // 24hr TTL
// Turn 1
const turn1 = await ps.sanitize(
"My name is John Smith. What should I know about my account?",
{ mode: "fake_substitute", conversationId: conv.conversationId },
);
const llmReply1 = await yourLLM(turn1.sanitizedText);
const response1 = await ps.restore(llmReply1, turn1.sessionId);
// Turn 2 — "John Smith" maps to the SAME fake name as turn 1
const turn2 = await ps.sanitize(
"What were you saying about John Smith earlier?",
{ mode: "fake_substitute", conversationId: conv.conversationId },
);
const llmReply2 = await yourLLM(turn2.sanitizedText);
const response2 = await ps.restore(llmReply2, turn2.sessionId);Error handling
import {
AuthenticationError,
QuotaExceededError,
NotFoundError,
ValidationError,
RateLimitError,
ServiceUnavailableError,
} from "raipii";
try {
const result = await ps.sanitize(text);
} catch (err) {
if (err instanceof AuthenticationError) {
// Invalid or missing API key
console.error("Check your API key at raipii.com");
} else if (err instanceof QuotaExceededError) {
// Monthly character limit reached
console.error("Upgrade your plan at raipii.com");
} else if (err instanceof NotFoundError) {
// Session expired or not found
console.error("Session expired — re-sanitize the original text");
} else if (err instanceof RateLimitError) {
// SDK retries automatically — this means retries were exhausted
console.error("Rate limit hit");
} else if (err instanceof ServiceUnavailableError) {
// SDK retried 3 times and failed
console.error("Service unavailable, try again shortly");
} else if (err instanceof ValidationError) {
console.error("Bad request:", err.message);
}
}All error classes extend RaipiiError which exposes .statusCode and .response.
Retry behaviour
The SDK automatically retries on 429 Too Many Requests and 503 Service Unavailable with exponential backoff:
| Attempt | Delay | |---|---| | 1st retry | 1s | | 2nd retry | 2s | | 3rd retry | 4s |
Default maxRetries: 3. Override:
const ps = new Raipii({ apiKey: "...", maxRetries: 5 });
const psNoRetry = new Raipii({ apiKey: "...", maxRetries: 0 });Client options
const ps = new Raipii({
apiKey: "ps_live_...", // or RAIPII_API_KEY env var
baseUrl: "https://api.raipii.com", // override for testing/proxies
timeoutMs: 30_000, // request timeout in ms
maxRetries: 3, // retries on 429/503
});TypeScript types
All methods are fully typed. Key interfaces:
interface SanitizeResult {
sessionId: string;
sanitizedText: string;
entitiesFound: EntityFound[];
charCount: number;
usage: { charsBilled: number };
conversationId: string | null;
}
interface RestoreResult {
restoredText: string;
substitutionsReversed: number;
usage: { charsBilled: number };
}
interface DetectResult {
entitiesFound: DetectedEntity[];
piiDetected: boolean;
riskLevel: "NONE" | "LOW" | "MEDIUM" | "HIGH";
usage: { charsBilled: number };
}
interface Conversation {
conversationId: string;
expiresAt: string;
}Caveats
- Session TTL — sessions expire after
sessionTtlseconds (default 1hr). Callingrestore()after expiry throwsNotFoundError. Call restore promptly after getting the LLM response. redactmode has no restore —[REDACTED]tokens contain no reversible information. Callingrestore()on a redact session returns the text unchanged.- Conversation TTL — conversation sessions expire after their TTL (default 24hr). After expiry, new turns start a fresh mapping.
- Characters billed —
sanitize,restore, anddetectall bill by character count of the input text. Free tier: 2M chars/month. - Starter tier detects structured PII reliably. Upgrade to Growth for enhanced contextual detection of names and addresses in free-form text.
ESM and CJS
The package ships both ESM and CommonJS builds:
// ESM (TypeScript / bundlers / Node with "type": "module")
import { Raipii } from "raipii";
// CJS (require)
const { Raipii } = require("raipii");Get an API key
Free tier — 2M characters/month, no credit card required: raipii.com
