npm package: @nodatachat/sdk
Description: NoData M2M SDK — Blind relay encryption, secure channels, and zero-knowledge delivery for machine-to-machine communication.
Latest version: 1.0.0

# @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

```bash
npm install @nodatachat/sdk
```

Zero dependencies. Works in Node.js, Bun, Deno, Cloudflare Workers, and any runtime with `fetch`.

---

## Quick start

```ts
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

```ts
const result = await nd.batchNative([
  { op: 'encrypt', field: 'phone',  value: '0501234567' },
  { op: 'encrypt', field: 'email',  value: 'david@company.com' },
  { op: 'encrypt', field: 'id',     value: '123456789' },
  { op: 'decrypt', field: 'ssn',    ciphertext: 'aes256gcm:v1:...' },
]);
// result.ok === 4, result.failed === 0
```

Or encrypt an entire object — deep walk, nested fields included:

```ts
const customer = {
  name: 'David',
  phone: '0501234567',
  email: 'david@company.com',
  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.

```ts
// 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.

```ts
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 forever
```

---

## Webhooks — encrypted at rest

Receive webhooks from Stripe, Twilio, or any service. Payloads encrypted before storage.

```ts
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.

```ts
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 auditor
```

---

## Vault — zero-knowledge storage

You encrypt client-side. Server stores blindly.

```ts
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

```ts
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:

```ts
// 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

```ts
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

```ts
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.

---

## Get your API key

**[nodatachat.com/developers](https://www.nodatachat.com/developers)**

---

<p align="center">
  <strong>NoData</strong> — Your server should never see what it doesn't need to know.
</p>