PrivacyPal SDK v1.0.2
Node.js Python

PrivacyPal SDK

The PrivacyPal SDK lets you encode sensitive data into Privacy Twins before it reaches an AI model, then decode the response to restore original values. Available for both Node.js and Python: identical API surface, same endpoint, same behavior. The same clients work against the enterprise data plane at api.privacypal.ai and the PrivacyPal Family data plane at api.family.privacypal.ai.

Node.js

@privacypal/sdk

Async/await API. Works with Express, Next.js, Fastify, and any Node.js runtime.

npm install @privacypal/sdk
Python

privacypal-sdk

Synchronous API built on requests. Works with FastAPI, Django, Flask, and any Python 3.10+ runtime.

pip install privacypal-sdk

Developer Quickstart

Zero to protected in five minutes: install, initialize, then run the full encode → AI → decode loop. Real PII never leaves your boundary; the model only ever sees Privacy Twins.

1. Install

npm install @privacypal/sdk
pip install privacypal-sdk

2. Get a key: sign up at the PrivacyPal Portal and export it as PRIVACYPAL_API_KEY.

3. Run the loop: this is a complete program. Encode, hand the safe text to any model or vendor, decode the answer.

quickstart
import { PrivacyPalClient } from '@privacypal/sdk';

const client = new PrivacyPalClient({
  apiUrl: 'https://api.privacypal.ai',
  apiKey: process.env.PRIVACYPAL_API_KEY,
});

// 1. Encode: real PII becomes Privacy Twins
const encoded = await client.encode({
  data: 'Refund request from Sarah Connor, sarah@acme.com, card ending 4242',
  sourceContainer: 'quickstart',
  sourceElement: 'demo',
});
console.log(encoded.encodedData); // safe for any LLM, vendor API, or log line

// 2. Use the protected text anywhere, e.g. the built-in AI gateway
const chat = await client.chatWithAI({
  prompt: 'Draft a polite reply approving the refund: ' + encoded.encodedData,
});

// 3. Decode: restore the original values, with an audit trail
const decoded = await client.decode({
  continuationId: encoded.continuationId,
  data: chat.decodedResponse ?? chat.llmResponse,
  sensitiveHashes: encoded.transformations.map(t => t.originalHash),
  authorization: { token: process.env.PRIVACYPAL_API_KEY, purpose: 'Quickstart demo' },
});
console.log(decoded.decodedData);
import os
from privacypal_sdk import PrivacyPalClient

client = PrivacyPalClient(
  api_url='https://api.privacypal.ai',
  api_key=os.environ['PRIVACYPAL_API_KEY'],
)

# 1. Encode: real PII becomes Privacy Twins
encoded = client.encode(
  'Refund request from Sarah Connor, sarah@acme.com, card ending 4242',
  source_container='quickstart',
  source_element='demo',
)
print(encoded['encodedData'])  # safe for any LLM, vendor API, or log line

# 2. Use the protected text anywhere, e.g. the built-in AI gateway
# Note: use prompt (not message) for chat_with_ai
chat = client.chat_with_ai(
  prompt='Draft a polite reply approving the refund: ' + encoded['encodedData'],
)

# 3. Decode: restore the original values, with an audit trail
decoded = client.decode(
  continuation_id=encoded['continuationId'],
  data=chat.get('decodedResponse') or chat['llmResponse'],
  sensitive_hashes=[t['originalHash'] for t in encoded['transformations']],
  authorization={'token': os.environ['PRIVACYPAL_API_KEY'], 'purpose': 'Quickstart demo'},
)
print(decoded['decodedData'])

That is the whole mental model: encode → transmit → decode. Everything else in this guide (batch, files, streaming, multi-turn sessions, the Family data plane) is a variation on that loop.

What's New in 1.0.2

Highlights since 1.0.0, across the SDKs and the platform API (now v1.4.4):

  • Developer keys: authenticate server-to-server with a developerKey (Node) / developer_key (Python) instead of a user JWT. Sent as x-pp-developer-key; rotate at runtime with updateDeveloperKey / update_developer_key.
  • Client IDs: optional clientId / client_id config, sent as x-pp-client-id to attribute traffic per integration.
  • Signup verification: register accepts an optional signup token for email-verified registration flows.
  • File encoding: encodeFile / encode_file accepts a platform identifier; Python adds a per-call timeout override for interactive callers (default upload timeout stays 120s).
  • Python reliability: health_check now reports a healthy API correctly (the /health endpoint returns plain text, not JSON), and errors are typed exceptions on a common PrivacyPalError base carrying status_code.
  • Entitlement leases (Python): on-device deployments can attach a lease via entitlement_lease / update_entitlement_lease, sent as x-pp-entitlement-lease and checked on the encode, batch, file, and decode routes.
  • Private Memory API: new authenticated /api/memory endpoint group (episodes, recall, stats, settings, audit, export, correction, and crypto-shred erase).
  • PrivacyPal Family: a dedicated Family data plane at api.family.privacypal.ai and a family detection profile that additionally protects school names and city-level geography. See the Family API section below.

Installation

Install the SDK for your language. Both packages expose identical functionality.

npm install @privacypal/sdk

Requires Node.js 18 or later. TypeScript types are included.

pip install privacypal-sdk

Requires Python 3.10 or later. Installs requests as the only dependency.

Initialization

Create a client instance with your API URL and API key. You can obtain an API key from the PrivacyPal Portal.

Server-to-server integrations can pass a developerKey (Node) / developer_key (Python) instead of a user JWT; it is sent as the x-pp-developer-key header and takes precedence when both are set. An optional clientId / client_id attributes traffic per integration.

initialize-client
import { PrivacyPalClient } from '@privacypal/sdk';

const client = new PrivacyPalClient({
  apiUrl: 'https://api.privacypal.ai',
  apiKey: process.env.PRIVACYPAL_API_KEY,
});

// Verify connectivity (optional)
const health = await client.healthCheck();
console.log(health.success); // true
from privacypal_sdk import PrivacyPalClient
import os

client = PrivacyPalClient(
  api_url='https://api.privacypal.ai',
  api_key=os.environ['PRIVACYPAL_API_KEY'],
)

# Verify connectivity (optional)
health = client.health_check()
print(health['success'])  # True

Encoding (Protect Data)

encode scans a string for PII and replaces sensitive values with Privacy Twins. Returns encodedData that is safe to pass to AI models, and a continuationId needed to decode later.

encode
const encoded = await client.encode({
  data: 'Patient: Jane Doe, DOB: 1985-03-15, SSN: 123-45-6789',
  sourceContainer: 'my-app',        // identifies your application
  sourceElement: 'patient-record',   // identifies the data field
  scoreThreshold: 0.35,              // PII confidence threshold (default)
  language: 'en',                    // language hint (default)
});

console.log(encoded.encodedData);    // "Patient: [TWIN-A1B2], DOB: [TWIN-C3D4]..."
console.log(encoded.continuationId); // keep this to decode later
console.log(encoded.transformations); // list of PII replacements made
encoded = client.encode(
  'Patient: Jane Doe, DOB: 1985-03-15, SSN: 123-45-6789',
  source_container='my-app',       # identifies your application
  source_element='patient-record',  # identifies the data field
  score_threshold=0.35,             # PII confidence threshold (default)
  language='en',                    # language hint (default)
)

print(encoded['encodedData'])     # "Patient: [TWIN-A1B2], DOB: [TWIN-C3D4]..."
print(encoded['continuationId'])  # keep this to decode later
print(encoded['transformations'])  # list of PII replacements made

To encode multiple items in one request, use encodeBatch / encode_batch. To encode a file (PDF, DOCX, CSV, image), use encodeFile / encode_file: it accepts an optional platform identifier, defaults to a 120s upload timeout, and (Python) takes a per-call timeout override for interactive callers.

Detection is policy-driven on the server. Accounts on the PrivacyPal Family data plane automatically get the family detection profile (school names and city-level geography protected, on top of standard PII); enterprise accounts get the standard profile. The profile is resolved from the authenticated account and cannot be switched from the client, so there is no SDK parameter for it, by design.

Decoding (Restore Data)

Pass the continuationId from the encode response alongside text containing Privacy Twins to restore original sensitive values.

decode
// Encode first
const encoded = await client.encode({
  data: 'Call me at 555-867-5309, I am Sarah Connor',
  sourceContainer: 'chatbot',
  sourceElement: 'user-message'
});

// Send encoded data to AI, get a response with Privacy Twins
const aiReply = await callOpenAI(encoded.encodedData);

// Decode the AI response, restore original values
const decoded = await client.decode({
  continuationId: encoded.continuationId,
  data: aiReply.content,
  sensitiveHashes: encoded.transformations.map(t => t.originalHash),
  authorization: { token: 'your-jwt', purpose: 'Display to user' },
});

console.log(decoded.decodedData); // Real names and numbers restored
# Encode first
encoded = client.encode(
  'Call me at 555-867-5309, I am Sarah Connor',
  source_container='chatbot',
  source_element='user-message'
)

# Send encoded data to AI, get a response with Privacy Twins
ai_reply = call_openai(encoded['encodedData'])

# Decode the AI response, restore original values
decoded = client.decode(
  continuation_id=encoded['continuationId'],
  data=ai_reply['content'],
  sensitive_hashes=[t['originalHash'] for t in encoded['transformations']],
  authorization={'token': 'your-jwt', 'purpose': 'Display to user'},
)

print(decoded['decodedData'])  # Real names and numbers restored

AI Chat (Built-in Encode/Decode)

Use chatWithAI to send prompts through PrivacyPal's AI gateway, which automatically encodes PII before forwarding to the LLM and decodes the response. For token-by-token output, Node.js adds chatWithAIStream (Server-Sent Events with per-chunk decoding); Python streaming is on the roadmap, so Python callers use the non-streaming chat_with_ai today. Multi-turn conversations carry twin mappings across turns via sessionContinuationIds and the ConversationSession helper.

chat-with-ai
const result = await client.chatWithAI({
  prompt: 'Summarise the patient notes for Jane Doe.',
  model: 'gemini-2.0-flash-exp',
  provider: 'vertex',
});

console.log(result.decodedResponse); // Decoded: real names in the answer
result = client.chat_with_ai(
  prompt='Summarise the patient notes for Jane Doe.',
  model='gemini-2.0-flash-exp',
  provider='vertex',
)

print(result['decodedResponse'])  # Decoded: real names in the answer

Family API

PrivacyPal Family runs on its own, fully isolated deployment of the PrivacyPal API. Two surfaces matter to developers:

  • Family data plane: https://api.family.privacypal.ai. The same API and SDKs documented here (encode, decode, AI chat, memory), deployed with entirely separate credentials and signing keys. An enterprise token is not valid on the Family plane and a Family member token is not valid on api.privacypal.ai, in either direction, by design.
  • Family platform API: https://family.privacypal.ai/api/family. Households, members, age stages, guardrails, devices, alerts, approvals, wellbeing, digests, and billing. Parents authenticate with a session (cookie or bearer token); kids sign in with a Family Code plus optional PIN and never hold an email or password; registered devices use a device token. Partner access is limited to read-only, aggregate admin endpoints.

Family member requests automatically use the family detection profile: on top of standard PII, school names (a dedicated SCHOOL entity) and city-level geography are twinned, because first name + school + city is a re-identification vector for a child. The platform enforces its product promises server-side: activity telemetry is signals-only (no prompt or response text can enter it), and teens in the Pilot stage (14 to 17) manage their own memory facts.

family-data-plane
// Same SDK, Family data plane. The member token comes from the
// Family platform (device member-token exchange), not from login().
const familyClient = new PrivacyPalClient({
  apiUrl: 'https://api.family.privacypal.ai',
  apiKey: memberToken,
});

const encoded = await familyClient.encode({
  data: 'Alex goes to Washington High in a sunny city',
  sourceContainer: 'family-app',
  sourceElement: 'kid-message',
});
// Family profile applies automatically: name, school, and city are all twinned.
# Same SDK, Family data plane. The member token comes from the
# Family platform (device member-token exchange), not from login().
family_client = PrivacyPalClient(
  api_url='https://api.family.privacypal.ai',
  api_key=member_token,
)

encoded = family_client.encode(
  'Alex goes to Washington High in a sunny city',
  source_container='family-app',
  source_element='kid-message',
)
# Family profile applies automatically: name, school, and city are all twinned.

The full Family platform surface is specified in family/openapi.yaml.

Error Handling

Node.js: Throws Error; check err.message for status prefixes. Python: Raises typed exceptions (AuthenticationError, TrialExpiredError, NetworkError, RequestError), all subclasses of PrivacyPalError, which carries status_code.

Exception / Pattern HTTP Status When it occurs
AuthenticationError / "401:"401Missing, expired, or invalid API key
TrialExpiredError / "403:" or "[Trial expired]"403Trial ended or subscription required
NetworkError / "Network Error:"N/ACannot reach the API (connection refused, DNS failure)
RequestError / "Request Error:"N/ARequest configuration error
error-handling
try {
  const encoded = await client.encode({ data: '...' });
} catch (err) {
  if (err.message?.startsWith('401:')) {
    console.error('Invalid or expired API key');
  } else if (err.message?.startsWith('403:') || err.message?.includes('Trial expired')) {
    console.error('Subscription required');
  } else if (err.message?.includes('Network Error')) {
    console.error('Cannot reach API');
  } else {
    throw err;
  }
}
from privacypal_sdk import (
  PrivacyPalClient,
  AuthenticationError,
  TrialExpiredError,
  NetworkError,
)

try:
  encoded = client.encode('...')
except AuthenticationError:
  print('Invalid or expired API key')
except TrialExpiredError:
  print('Subscription required')
except NetworkError:
  print('Cannot reach API')
except Exception as e:
  raise

Examples

Real workflows from production deployments: encode at the boundary, hand twins to an external agent or API, decode the response back to real values.

Agent entry point for secure A2A communication

Encode the sensitive input, hand the twins off to an external agent, then decode the response back to original values.

agent-a2a
const encoded = await client.encode({
  data: 'User: John Smith, Email: john@acme.com, ID: user-123',
  sourceContainer: 'my-agent',
  sourceElement: 'user_input',
});

// Share encoded data with an external agent
const response = await sendToExternalAgent(encoded.encodedData);

// Decode the response back to real values
const decoded = await client.decode({
  continuationId: encoded.continuationId,
  data: response.text,
});

console.log(decoded.decodedData);
encoded = client.encode(
  'User: John Smith, Email: john@acme.com, ID: user-123',
  source_container='my-agent',
  source_element='user_input',
)

# Share encoded data with an external agent
response = send_to_external_agent(encoded['encodedData'])

# Decode the response back to real values
decoded = client.decode(
  continuation_id=encoded['continuationId'],
  data=response['text'],
)

print(decoded['decodedData'])

Healthcare AI: patient data to a 3rd-party analyzer

Encode PHI before sending to a vendor API, decode the analysis on the way back. HIPAA-compliant by design.

healthcare-ai
const encoded = await client.encode({
  data: 'Patient: Jane Doe, DOB: 1985-03-15, Dx: Condition X',
  sourceContainer: 'ehr-system',
  sourceElement: 'patient-record',
});

const analysis = await fetch('https://vendor-api.com/analyze', {
  method: 'POST',
  body: JSON.stringify({ data: encoded.encodedData }),
});

const result = await client.decode({
  continuationId: encoded.continuationId,
  data: (await analysis.json()).report,
});
encoded = client.encode(
  'Patient: Jane Doe, DOB: 1985-03-15, Dx: Condition X',
  source_container='ehr-system',
  source_element='patient-record',
)

response = requests.post(
  'https://vendor-api.com/analyze',
  json={'data': encoded['encodedData']},
)

result = client.decode(
  continuation_id=encoded['continuationId'],
  data=response.json()['report'],
)

Financial services: decentralized dark pool x402 coming soon

Encode trade info to protect trader identity, match through Privacy Twins, and (in a future release) decode only after x402 payment is settled.

dark-pool
// Future: import { x402Middleware } from '@x402/payment';

const encoded = await client.encode({
  data: 'Trader: trader-789, Symbol: AAPL, Qty: 1000',
  sourceContainer: 'dark-pool',
  sourceElement: 'trade-request',
});

const match = await findCounterparty(encoded.encodedData);

if (match.found) {
  // Future: x402 payment required before decode
  const realData = await client.decode({
    continuationId: encoded.continuationId,
    data: match.counterpartyData,
  });
  return realData.decodedData;
}
# Future: from x402 import middleware

encoded = client.encode(
  'Trader: trader-789, Symbol: AAPL, Qty: 1000',
  source_container='dark-pool',
  source_element='trade-request',
)

match = find_counterparty(encoded['encodedData'])

if match['found']:
  # Future: x402 payment required before decode
  real_data = client.decode(
    continuation_id=encoded['continuationId'],
    data=match['counterpartyData'],
  )
  return real_data['decodedData']

Full API Reference

Explore all endpoints, request/response schemas, and try the API interactively. Open API Reference →