Usage

Integrating Nen is four steps: mount the session routes, encrypt requests from the client, wrap the endpoints you want decrypted, and handle errors. All snippets below match the shipped @withnen/client and @withnen/server APIs.

1. Mount the session routes

The client needs four session endpoints under /api/nen: handshake, rotate, terminate, and status. A single dynamic Route Handler covers all of them.

Create src/app/api/nen/[action]/route.ts:

import {
  handleHandshake,
  handleTerminate,
  handleStatus,
  handleRotate,
  setSessionStore,
  InMemorySessionStore,
  // RedisSessionStore,   // any node/serverless runtime
  // UpstashSessionStore, // Edge runtimes (Workers, Vercel Edge) — REST, no TCP
} from '@withnen/server';

// ── Session store — pick one based on your runtime ──────────────────────────
//
// InMemorySessionStore   → dev / single-instance only. Sessions live in
//                          process memory; a second instance or a cold start
//                          will not find the session → ISO-2002.
//
// RedisSessionStore      → any Node.js / serverless runtime with TCP.
//                          Pass your ioredis / node-redis client.
//
// UpstashSessionStore    → Edge runtimes (Vercel Edge, Cloudflare Workers).
//                          Uses the Upstash REST API over fetch — no TCP,
//                          no extra dependency. Set two env vars and done.
//
setSessionStore(new InMemorySessionStore()); // ← change this before going to prod

// setSessionStore(new RedisSessionStore(redisClient));

// setSessionStore(new UpstashSessionStore(
//   process.env.UPSTASH_REDIS_REST_URL!,
//   process.env.UPSTASH_REDIS_REST_TOKEN!,
// ));

export async function POST(req: Request, { params }: { params: Promise<{ action: string }> }) {
  const { action } = await params;
  switch (action) {
    case 'handshake': return handleHandshake(req);
    case 'rotate':    return handleRotate(req);
    case 'terminate': return handleTerminate(req);
    default:          return new Response('Not Found', { status: 404 });
  }
}

export async function GET(req: Request, { params }: { params: Promise<{ action: string }> }) {
  const { action } = await params;
  if (action === 'status') return handleStatus(req);
  return new Response('Not Found', { status: 404 });
}

2. Encrypt requests from the client

Construct an NenClient with your server origin (use '' for same-origin), run the handshake once, then call nenFetch. The request body is encrypted and the JSON response is decrypted for you.

import { NenClient } from '@withnen/client';

const client = new NenClient('', {
  identityMode: 'pqc', // optional: adds a one-time ML-DSA identity signature
});

await client.handshake(); // ML-KEM key exchange — run once per session

// nenFetch mirrors fetch(). For a JSON response it returns the decrypted object.
const data = await client.nenFetch('/api/secure-data', {
  method: 'POST',
  body: JSON.stringify({ secret: 'Only the endpoint that needs this can read it' }),
});

console.log(data); // { message: 'Securely processed', received: { ... } }

Prefer a zero-ceremony helper? createNenFetch handshakes lazily on first use:

import { createNenFetch } from '@withnen/client';

const nenFetch = createNenFetch(''); // same-origin
const data = await nenFetch('/api/secure-data', {
  method: 'POST',
  body: JSON.stringify({ amount: 1840 }),
});

3. Protect server endpoints

Wrap any route with withNen. The middleware verifies the per-request HMAC, enforces the replay window, decrypts the body, and encrypts whatever you return.

import { withNen } from '@withnen/server';

export const POST = withNen(async (req, body) => {
  // `body` is already decrypted and the request is already authenticated.
  return { message: 'Securely processed', received: body };
});

HMAC is mandatory by default — a request without a valid signature is rejected with ISO-3001. Only set withNen(handler, { strict: false }) for explicitly opted-in legacy clients that cannot sign requests.

4. Session rotation

Call rotate to replace an existing session without disrupting the user — old session destroyed, new keys negotiated, new sid returned. Do this on a timer or after a sensitive action.

// client
await client.rotate(); // tears down old session, handshakes a new one
// All subsequent nenFetch calls automatically use the new session

On the server the handleRotate in your route handler takes care of the rest — no extra code needed.

5. Stream securely (SSE)

For LLM tokens or any server-sent stream, return an async iterable from withNenStream and read it with nenStream — each chunk is encrypted independently.

// server
import { withNenStream } from '@withnen/server';

export const POST = withNenStream(async (req, body) => {
  async function* tokens() {
    for (const word of ['secure', 'streaming', 'works']) yield word + ' ';
  }
  return tokens();
});
// client
for await (const chunk of client.nenStream('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ prompt }),
})) {
  process.stdout.write(chunk); // decrypted token-by-token
}

6. Error handling

Every Nen failure throws a NenError with a stable ISO-xxxx code. Catch it by code to give users meaningful feedback — and log the detail for yourself without leaking it to the wire.

import { NenError } from '@withnen/client'; // or '@withnen/server'

try {
  const data = await nenFetch('/api/secure-data', {
    method: 'POST',
    body: JSON.stringify({ amount: 1840 }),
  });
} catch (err) {
  if (err instanceof NenError) {
    switch (err.code) {
      case 'ISO-2002': // session expired
        await client.handshake(); // re-establish and retry
        break;
      case 'ISO-3001': // HMAC missing — should not happen with the SDK
      case 'ISO-3002': // HMAC mismatch — request tampered
        console.error('Auth failure', err.code, err.message);
        break;
      default:
        console.error('Nen error', err.code, err.message);
    }
  }
}

The server-side error response body is always { "error": { "code", "message" } }. The diagnosis hint is logged server-side only — it is never sent to the client. See the Error codes reference for every ISO-xxxx code with its cause and fix.

Next: see the Protocol spec for the exact wire format, the API reference for every export, or the Error codes reference.