Error codes
Every failure inside the Nen layer carries a stable ISO-xxxx code. Paste the code; this page tells you exactly what happened and the fix. Each code has its own anchor — deep-link straight to it, e.g. /docs/error-codes#iso-3001.
{ "error": { "code", "message" } } — a safe, generic message only. The precise diagnosis below is logged server-side as a hint and is never sent over the wire. Codes are a stable contract: never reused or renumbered. The single source of truth is ERROR_CODES.md, mirrored by both SDK errors.ts catalogs.1xxxHandshake / key exchange
Handshake body had neither `pk` nor `publicKey`. Client SDK out of date, or the request wasn't made by an Nen client.
ML-KEM encapsulation/decapsulation threw. Malformed/wrong-length public key, or a Wasm load failure.
Couldn't reach /api/nen/handshake. Wrong serverUrl, server down, or CORS.
Handshake responded non-2xx or without sid/ct. The route isn't wired to handleHandshake().
2xxxSession lifecycle
nenFetch/nenStream called before a successful handshake().
Server has no entry for X-Nen-Session. Expired by TTL, evicted, or this node never saw the handshake (use a shared/stateless store).
No X-Nen-Session header. Not an Nen client, or a proxy stripped it.
3xxxAuthentication (HMAC / identity)
No X-Nen-Signature on a session that requires HMAC. HMAC is mandatory — this is the auth-downgrade guard.
HMAC over METHOD\nPATH\nTIMESTAMP\nNONCE didn't match. Tampered request, wrong key, or a canonical-string mismatch (commonly path-vs-full-URL).
X-Nen-Timestamp is >30s from server time. Clock skew or a replayed/delayed request.
Optional ML-DSA identity signature over the ephemeral key didn't verify. Wrong identity key or a MITM at handshake.
4xxxCryptography (AEAD / payload)
ChaCha20-Poly1305 AEAD tag verification failed. Tampered/truncated ciphertext, or a desynced shared secret (try rotate()).
AEAD sealing of the response threw. Usually a corrupt/missing shared secret.
Decryption succeeded but the plaintext wasn't valid JSON.
5xxxReplay / nonce
This nonce was already seen for the session. A legitimate identical retry, or an actual replay.
6xxxWire format / encoding
7xxxStreaming
9xxxInternal / unknown
Unclassified failure wrapped by NenError.from(). The original error is in the logged detail.
Programmatic lookup
Both SDKs export a reverse lookup so tooling and support can resolve a code from a log:
import { describeNenCode } from '@withnen/server'; // or '@withnen/client'
describeNenCode('ISO-3001');
// → { code: 'ISO-3001', status: 401, message: '…', hint: '…' }See also the protocol spec and the threat model.