@nodatachat/sdk
v1.0.0
Published
NoData M2M SDK — Blind relay encryption, secure channels, and zero-knowledge delivery for machine-to-machine communication.
Maintainers
Readme
@nodatachat/sdk
Your server should never see what it doesn't need to know.
NoData is a blind relay. Data passes through — encrypted, audited, untouched. Your server stores ciphertext. NoData stores nothing. Not the key. Not the plaintext. Not even metadata about what was encrypted.
One package. Five lines. Full SOC compliance.
Install
npm install @nodatachat/sdkZero dependencies. Works in Node.js, Bun, Deno, Cloudflare Workers, and any runtime with fetch.
Quick start
import { NoData } from '@nodatachat/sdk';
const nd = new NoData({ apiKey: 'sk_live_...' });
// Encrypt a credit card — server-blind
const { ciphertext } = await nd.encrypt({
field: 'credit_card',
value: '4111111111111111',
});
// Store `ciphertext` in YOUR database. NoData stores nothing.
// Later — decrypt
const { value } = await nd.decrypt({
field: 'credit_card',
ciphertext,
requester: 'billing-service',
});That's it. AES-256-GCM. Audit trail. Zero plaintext on any server.
What this does
| You call | What happens | What NoData stores |
|----------|-------------|-------------------|
| nd.encrypt() | AES-256-GCM encryption, key material returned to you | Nothing |
| nd.decrypt() | Decryption, audit log entry created | Metadata only (who, when, which field) |
| nd.channel.create() | Encrypted tunnel between two systems | Channel metadata, no content |
| nd.deliver.send() | Burn-after-read secret delivery | Encrypted blob, deleted on read |
| nd.evidence.query() | Returns audit trail for compliance | - |
Batch — 100 fields, one call
const result = await nd.batchNative([
{ op: 'encrypt', field: 'phone', value: '0501234567' },
{ op: 'encrypt', field: 'email', value: '[email protected]' },
{ op: 'encrypt', field: 'id', value: '123456789' },
{ op: 'decrypt', field: 'ssn', ciphertext: 'aes256gcm:v1:...' },
]);
// result.ok === 4, result.failed === 0Or encrypt an entire object — deep walk, nested fields included:
const customer = {
name: 'David',
phone: '0501234567',
email: '[email protected]',
address: { street: '123 Main', city: 'Tel Aviv' },
age: 30,
};
const encrypted = await nd.encryptObject(customer, ['phone', 'email']);
// { name: 'David', phone: 'aes256gcm:v1:...', email: 'aes256gcm:v1:...', address: {...}, age: 30 }Secure channels — system to system
Two systems need to exchange sensitive data. Neither trusts the other's infrastructure.
// System A — creates a channel
const ch = await nd.channel.create({
ttl: '1h',
requireVerification: true,
});
// Share ch.channel_token with System B
// Share ch.verification_code with the human approver
// System B — sends the data
await nd.channel.send({
token: ch.channel_token,
data: { card: '4111111111111111', cvv: '123', exp: '12/28' },
burnAfterRead: true,
});
// System A — receives
const result = await nd.channel.receive({
token: ch.channel_token,
verificationCode: '482901',
});
const card = JSON.parse(result.data);The server relays encrypted bytes. It never sees the card number.
Burn-after-read delivery
Send a secret. Recipient opens the link. Content self-destructs.
const d = await nd.deliver.send({
content: 'DATABASE_PASSWORD=s3cr3t_p@ss!',
ttl: '30m',
burn: true,
maxViews: 1,
});
console.log(d.delivery_url);
// https://www.nodatachat.com/d/abc123
// Opened once → gone foreverWebhooks — encrypted at rest
Receive webhooks from Stripe, Twilio, or any service. Payloads encrypted before storage.
const wh = await nd.webhook.create({
label: 'stripe-payments',
ttl: '30d',
});
// Point Stripe to: wh.ingest_url
// Store wh.secret — you need it to decrypt
// Poll for events
const events = await nd.webhook.events(wh.channel_token);Evidence — SOC audit trail
Every encrypt/decrypt is logged. Metadata only — never content.
const trail = await nd.evidence.query({
field: 'credit_card',
action: 'decrypt',
from: '2026-03-01',
to: '2026-03-27',
});
// WHO decrypted WHAT field WHEN — ready for SOC 2 auditorVault — zero-knowledge storage
You encrypt client-side. Server stores blindly.
const vault = await nd.vault.create({
label: 'customer-contracts',
encrypted_blob: myEncrypt(data, myKey),
iv: myIv,
metadata: { customer_id: 'cust_123' },
});
const entry = await nd.vault.read(vault.vault_id);
const data = myDecrypt(entry.encrypted_blob, entry.iv, myKey);Express plugin — one line
import express from 'express';
import { nodataExpress } from '@nodatachat/sdk/express';
const app = express();
app.use('/api/customers', nodataExpress({
apiKey: 'sk_live_...',
encryptFields: ['phone', 'email', 'id_number'],
decryptFields: ['phone', 'email'],
decryptHeader: 'X-NoData-Decrypt',
}));
app.get('/api/customers', async (req, res) => {
const rows = await db.query('SELECT * FROM customers');
res.json(rows);
// phone, email, id_number → automatically encrypted in response
});Also available: @nodatachat/sdk/fastify.
Service accounts — scoped M2M keys
Your master key creates restricted child keys for each service:
// POST /api/v1/service-account
const svc = await fetch('https://www.nodatachat.com/api/v1/service-account', {
method: 'POST',
headers: { Authorization: 'Bearer sk_live_MASTER_KEY' },
body: JSON.stringify({
name: 'payment-service',
scopes: ['encrypt', 'decrypt'],
rate_limit_per_minute: 500,
daily_quota: 100000,
expires_in_days: 90,
allowed_ips: ['10.0.0.0/8'],
}),
});
// { key: "svc_live_abc123..." } — shown ONCE| Key prefix | Purpose | Can create child keys |
|-----------|---------|----------------------|
| sk_live_ | Master key | Yes |
| svc_live_ | Service account | No |
Configuration
const nd = new NoData({
apiKey: 'sk_live_...',
baseUrl: 'https://www.nodatachat.com', // default
timeout: 30000, // ms
retries: 2, // on 5xx / network error
headers: { 'X-Custom': 'value' }, // added to every request
// Hooks
onRequest: (url, init) => console.log(`→ ${url}`),
onResponse: (url, status, ms) => console.log(`← ${status} ${ms}ms`),
onError: (url, err) => console.error(`! ${url}: ${err.message}`),
});Error handling
import { NoData, NoDataError } from '@nodatachat/sdk';
try {
await nd.encrypt({ field: 'ssn', value: '123-45-6789' });
} catch (err) {
if (err instanceof NoDataError) {
console.log(err.status); // 429
console.log(err.message); // "Rate limit exceeded"
console.log(err.retryAfter); // 45 (seconds)
}
}Rate limits are handled automatically — the SDK waits and retries on 429.
API reference
Core
| Method | Description |
|--------|------------|
| nd.encrypt(req) | Encrypt a single field |
| nd.decrypt(req) | Decrypt a single field |
| nd.batchEncrypt(fields) | Parallel encrypt (N HTTP calls) |
| nd.batchDecrypt(fields) | Parallel decrypt (N HTTP calls) |
| nd.batchNative(items) | Single-call batch (1 HTTP call, up to 100 ops) |
| nd.encryptObject(obj, fields) | Deep-walk encrypt matching fields |
| nd.decryptObject(obj, fields) | Deep-walk decrypt matching fields |
| nd.ping() | Health check + latency |
Channels
| Method | Description |
|--------|------------|
| nd.channel.create(opts) | Create encrypted channel |
| nd.channel.send(req) | Send data through channel |
| nd.channel.receive(req) | Receive data from channel |
| nd.channel.verify(token, code) | Verify recipient |
| nd.channel.proof(token) | Get delivery proof |
Webhooks
| Method | Description |
|--------|------------|
| nd.webhook.create(opts) | Create webhook channel |
| nd.webhook.events(token) | Poll encrypted events |
| nd.webhook.list() | List all channels |
Delivery
| Method | Description |
|--------|------------|
| nd.deliver.send(req) | Burn-after-read delivery |
| nd.deliver.burn(content) | Anonymous burn drop |
| nd.deliver.read(dropId) | Read + self-destruct |
Evidence
| Method | Description |
|--------|------------|
| nd.evidence.query(q) | Query audit trail |
| nd.evidence.certificate(id) | Proof certificate |
| nd.evidence.verify(hash) | Verify proof |
Vault
| Method | Description |
|--------|------------|
| nd.vault.create(req) | Store encrypted blob |
| nd.vault.read(id) | Retrieve encrypted blob |
| nd.vault.delete(id) | Delete entry |
| nd.vault.status() | Storage stats |
Architecture
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Your App │ ──────→ │ NoData API │ ──────→ │ Your DB │
│ │ │ │ │ │
│ plaintext │ │ ciphertext │ │ ciphertext │
│ lives here │ │ passes thru │ │ stored here│
│ │ │ stores: 0 │ │ │
└─────────────┘ └──────────────┘ └─────────────┘NoData is a relay, not a vault. Data flows through, gets encrypted, and leaves. The server is blind by design — not by policy, by architecture.
Why
Most encryption SDKs ask you to trust their server. We don't.
- No plaintext on our servers — ever
- No key storage — you hold the keys
- No data retention — we're a relay, not a warehouse
- Full audit trail — every operation logged (metadata only)
- SOC 2 ready — evidence endpoint = auditor-friendly
The simplest way to add field-level encryption to any system. Five lines of code. Zero trust required.
