BREAKING CHANGE: keyDir now defaults to null (ephemeral) instead of
'client_keys'. Clients that relied on keys persisting across restarts
must now pass keyDir explicitly.
The Python SDK defaults to key_dir=None: a key pair is generated in
memory for the session and never written to disk. The JS port defaulted
to 'client_keys' and always persisted, so merely constructing a client
wrote an RSA private key into the working directory. That is a weaker
default than the client it ports, and one users never asked for.
- keyDir?: string | null, defaulting to undefined. null and undefined
both mean ephemeral, matching Python's None.
- Persistent mode is unchanged when keyDir is set: load the existing
pair from that directory, otherwise generate and save one there.
- Browsers are always ephemeral; they have no filesystem.
Key rotation follows the same rule. It previously hardcoded
'client_keys' as its fallback directory, so an ephemeral client would
have started writing private keys to disk on the first rotation tick.
Rotated keys are now persisted only where keyDir or keyRotationDir is
explicitly configured.
Tests assert the intent (that saveKeys is never called) rather than
probing the filesystem, since a leftover client_keys/ from the old
default would otherwise make them pass or fail for the wrong reason.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.2 KiB
Troubleshooting
Authentication Errors
AuthenticationError: Invalid or missing API key
The server rejected your API key.
Causes and fixes:
- Key not set — pass
apiKeyto the constructor or useprocess.env.NOMYO_API_KEY. - Key has leading/trailing whitespace — check the value with
console.log(JSON.stringify(process.env.NOMYO_API_KEY)). - Key contains CR or LF characters — the client rejects keys with
\ror\nand throwsSecurityErrorbefore the request is sent. Regenerate the key.
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY, // never hardcode
});
Connection Errors
APIConnectionError: Network error / connect ECONNREFUSED
The client could not reach the router.
Check:
baseUrlis correct — the default ishttps://api.nomyo.ai(port 12435).- You have network access to the host.
- TLS is not being blocked by a proxy or firewall.
SecurityError: HTTPS is required
You passed an http:// URL without setting allowHttp: true.
// Local dev only
const client = new SecureChatCompletion({
baseUrl: 'http://localhost:12435',
allowHttp: true,
});
Never set allowHttp: true in production — the server public key fetch and all request data would travel unencrypted.
APIConnectionError: Request timed out
The default timeout is 60 seconds. Larger models or busy endpoints may need more:
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
timeout: 120000, // 2 minutes
});
Key Loading Failures
Error: Failed to load keys: no such file or directory
The keyDir directory or the PEM files inside it don't exist. This only applies when you opt into persistence — by default keys are ephemeral and no directory is touched at all. On first run with a keyDir set, the library generates and saves a new key pair there automatically, so make sure the directory is writable:
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
keyDir: '/var/lib/myapp/nomyo-keys', // directory must exist and be writable
});
Error: Invalid passphrase / Error: Failed to decrypt private key
The password you passed to loadKeys() or keyRotationPassword doesn't match what was used to encrypt the file.
await client.loadKeys(
'client_keys/private_key.pem',
'client_keys/public_key.pem',
process.env.NOMYO_KEY_PASSWORD, // must match the password used on generateKeys()
);
Error: RSA key too small
The library enforces a minimum key size of 2048 bits. If you have old 1024-bit keys, regenerate them:
await client.generateKeys({
saveToFile: true,
keyDir: 'client_keys',
keySize: 4096, // recommended
});
Error: Failed to load keys (browser)
Key loading from files is a Node.js-only feature. In browsers, keys are generated in memory on first use. Do not call loadKeys() in a browser context.
Rate Limit Errors
RateLimitError: Rate limit exceeded
All automatic retries were exhausted. The default limit is 2 requests/second; burst allows 4 requests/second once per 10-second window.
Fixes:
- Reduce concurrency — avoid large
Promise.allbatches. - Add client-side throttling (see Rate Limits).
- Increase
maxRetriesso the client backs off longer before giving up:
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
maxRetries: 5,
});
ServiceUnavailableError with 30-minute cool-down
Burst limits were hit repeatedly and a cool-down was applied to your key. Wait 30 minutes, then review your request patterns.
Model / Tier Errors
ForbiddenError: Model not allowed for this security tier
The model you requested is not available at the security tier you specified. Try a lower tier or a different model:
// If 'maximum' tier rejects the model, try 'high' or 'standard'
const response = await client.create({
model: 'Qwen/Qwen3.5-27B',
messages: [...],
security_tier: 'high', // try 'standard' if still rejected
});
See Models — Security Tier Compatibility for details.
Crypto / Security Errors
SecurityError: Decryption failed
The response could not be decrypted. This is intentionally vague to avoid leaking crypto details.
Possible causes:
- The server returned a malformed response (check
debug: trueoutput). - A network proxy modified the response body.
- The server's public key changed mid-session — the next request will re-fetch it automatically.
Enable debug mode to log the raw response and narrow the cause:
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
debug: true,
});
Error: Unsupported protocol version / Error: Unsupported encryption algorithm
The server sent a response in a protocol version or with an encryption algorithm not supported by this client version. Update the package:
npm update nomyo-js
DisposedError: Method called after dispose()
You called a method on a client that has already been disposed.
client.dispose();
await client.create(...); // throws DisposedError
Create a new client instance if you need to make more requests after disposal.
Memory Protection Warnings
getMemoryProtectionInfo() returns method: 'zero-only'
This is normal for a pure JavaScript installation. The library zeroes sensitive buffers immediately after use but cannot lock pages to prevent swapping (OS mlock requires a native addon).
import { getMemoryProtectionInfo } from 'nomyo-js';
const info = getMemoryProtectionInfo();
// { method: 'zero-only', canLock: false, isPlatformSecure: false }
For environments where swap-file exposure is unacceptable (HIPAA PHI, classified data), install the optional nomyo-native addon or run on a system with swap disabled.
Node.js-Specific Issues
ReferenceError: crypto is not defined
In CommonJS modules on Node.js before v19, crypto is not a global. Import it explicitly:
// CommonJS
const { webcrypto } = require('crypto');
global.crypto = webcrypto;
// Or switch to ES modules (recommended)
// package.json: "type": "module"
The library itself imports crypto correctly — this error only appears if your own application code tries to use crypto directly.
SyntaxError: Cannot use import statement in a module / CommonJS vs ESM
The package ships both CommonJS (dist/node/) and ESM (dist/esm/) builds. Node.js selects the correct one automatically via package.json exports. If you see import errors, check that your package.json or bundler is not forcing the wrong format.
For ESM: set "type": "module" in your package.json or use .mjs file extensions.
For CommonJS: use require('nomyo-js') or .cjs extensions.
TypeScript: Cannot find module 'nomyo-js' / missing types
Ensure your tsconfig.json includes "moduleResolution": "bundler" or "moduleResolution": "node16" and that nomyo-js is in dependencies (not just devDependencies):
npm install nomyo-js
Browser-Specific Issues
Content Security Policy blocked
If your app's CSP restricts script-src or connect-src, add the NOMYO API domain:
Content-Security-Policy: connect-src https://api.nomyo.ai;
TypeError: Failed to fetch (CORS)
The NOMYO API includes CORS headers. If you see CORS errors in a browser, verify the baseUrl is correct (HTTPS, correct port) and that no browser extension is blocking the request.
Keys not persisted across page reloads
This is expected behaviour — browsers do not have file system access. Keys are generated fresh on each page load. If you need persistent keys in a browser context, implement your own loadKeys/generateKeys wrapper using localStorage or IndexedDB (not recommended for high-security scenarios).
Debugging Tips
Enable verbose logging
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
debug: true,
});
Debug mode logs: key generation/loading, server public key fetches, request encryption details, retry attempts, and response decryption.
Check memory protection status
import { getMemoryProtectionInfo } from 'nomyo-js';
console.log(getMemoryProtectionInfo());
Inspect response metadata
The _metadata field in every response carries server-side diagnostics:
const response = await client.create({ ... });
console.log(response._metadata);
// {
// payload_id: '...',
// is_encrypted: true,
// encryption_algorithm: 'hybrid-aes256-rsa4096',
// security_tier: 'standard',
// memory_protection: { ... },
// }
Test with minimum configuration
Strip all optional configuration and test with the simplest possible call to isolate the issue:
import { SecureChatCompletion } from 'nomyo-js';
const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY });
const r = await client.create({
model: 'Qwen/Qwen3-0.6B',
messages: [{ role: 'user', content: 'ping' }],
});
console.log(r.choices[0].message.content);
client.dispose();