Official Node.js / TypeScript SDK for NessyAPI — clinical decision support API for healthcare applications.
fetch + node:crypto)npm install @nessyapi/sdk
# or
pnpm add @nessyapi/sdk
# or
yarn add @nessyapi/sdk
import { NessyClient } from "@nessyapi/sdk";
const client = new NessyClient({ apiKey: process.env.NESSYAPI_KEY! });
// Create a session
const session = await client.sessions.create({
chiefComplaint: "headache",
age: 35,
sex: "male",
});
// Answer questions
let current = session.current_question;
while (current) {
const response = await client.sessions.answer(session.session_id, current.question_id, {
rawText: "since yesterday, pressure behind eyes",
});
if (response.is_complete) break;
current = response.current_question;
}
// Finalize and read results
const results = await client.sessions.finalize(session.session_id);
console.log(results.triage_level, results.differentials);
Or use the convenience method that does the full loop:
const results = await client.sessions.runAssessment({
chiefComplaint: "headache",
age: 35,
answers: { q_onset: "yesterday", q_severity: "8" },
});
| Group | Methods |
|---|---|
client.sessions |
create, answer, route, getResults, getState, finalize, getQuestions, updateDemographics, submitFeedback, rectify, delete, exportSession, list, runAssessment |
client.patients |
get, update, listSessions, delete |
client.admin |
balance, usage, usageDetails, stats, rateLimit, auditLog, getTier, listTiers, setTier, listKeys, createKey, rotateKey, revokeKey, getNlpSettings, setNlpSettings |
client.webhooks |
create, list, delete, rotate, test, deliveries |
client.team |
list, invite, changeRole, remove |
client.schema |
chiefComplaints, branches |
List methods return an AsyncIterable that auto-paginates:
for await (const session of client.sessions.list({ status: "finalized" })) {
console.log(session.session_id, session.triage_level);
}
// Or fetch one page at a time
const page = await client.sessions.list({ limit: 50 }).page(0);
console.log(`Showing ${page.data.length} of ${page.total}`);
// Or materialize to array (capped at 10_000 by default)
const all = await client.sessions.list().toArray(500);
All errors extend typed subclasses. Use instanceof instead of checking status codes:
import {
NessyAuthenticationError,
NessyRateLimitError,
NessyValidationError,
NessyAPIError,
} from "@nessyapi/sdk";
try {
await client.sessions.create({ chiefComplaint: "headache" });
} catch (err) {
if (err instanceof NessyRateLimitError) {
console.warn(`Rate limited. Retry after ${err.retryAfterMs}ms`);
} else if (err instanceof NessyValidationError) {
console.error(`Invalid input:`, err.fieldErrors);
} else if (err instanceof NessyAuthenticationError) {
console.error("Check your API key");
} else if (err instanceof NessyAPIError) {
console.error(`API error ${err.status} (${err.errorCode}) — request_id=${err.requestId}`);
} else {
throw err;
}
}
Webhook signatures use HMAC-SHA256 with a timestamp to prevent replay attacks. Use the dedicated subpath import so webhook receivers don't bundle the full client:
import { verifyWebhookSignature, NessyWebhookSignatureError } from "@nessyapi/sdk/webhook";
// Express
import express from "express";
const app = express();
app.post("/webhooks/nessy", express.raw({ type: "application/json" }), (req, res) => {
try {
verifyWebhookSignature({
payload: req.body, // Buffer, NOT parsed JSON
secret: process.env.NESSY_WEBHOOK_SECRET!,
signatureHeader: req.header("x-nessyapi-signature-256")!,
timestampHeader: req.header("x-nessyapi-timestamp")!,
});
} catch (err) {
if (err instanceof NessyWebhookSignatureError) return res.status(401).end();
throw err;
}
const event = JSON.parse(req.body.toString("utf8"));
// handle event...
res.status(200).end();
});
// Next.js App Router
export async function POST(req: Request) {
const payload = Buffer.from(await req.arrayBuffer());
verifyWebhookSignature({
payload,
secret: process.env.NESSY_WEBHOOK_SECRET!,
signatureHeader: req.headers.get("x-nessyapi-signature-256")!,
timestampHeader: req.headers.get("x-nessyapi-timestamp")!,
});
// ...
}
// Fastify
fastify.addContentTypeParser(
"application/json",
{ parseAs: "buffer" },
(_req, body, done) => done(null, body),
);
fastify.post("/webhooks/nessy", async (req, reply) => {
verifyWebhookSignature({
payload: req.body as Buffer,
secret: process.env.NESSY_WEBHOOK_SECRET!,
signatureHeader: req.headers["x-nessyapi-signature-256"] as string,
timestampHeader: req.headers["x-nessyapi-timestamp"] as string,
});
// ...
});
Timestamp tolerance defaults to 300 seconds. If you don't want replay protection (not recommended):
verifyWebhookSignature({ /* ... */, toleranceSeconds: 0 });
const client = new NessyClient({
apiKey: "nsy_live_...", // required
baseUrl: "https://api.custom.com", // optional, defaults to prod
timeoutMs: 30_000, // per-attempt, default 30s
retries: 3, // or { maxRetries, initialDelayMs, maxDelayMs }
userAgent: "my-app/1.0", // appended to built-in UA
logger: console, // or pino/winston/bunyan (duck-typed)
fetch: undici.fetch.bind(agent), // DI for connection pooling, testing
defaultHeaders: { "x-custom-trace": "..." },
});
The SDK retries on 408, 429, and 5xx responses plus network errors. It never retries on other 4xx (idempotency is preserved via stable Idempotency-Key across attempts).
Backoff is exponential with full jitter:
delay = min(initialDelayMs * 2^attempt, maxDelayMs) * (0.5 + random() * 0.5)
Capped at 60 seconds per step. Defaults to 3 retries. Retry-After header is respected on 429.
| Threat | Defense |
|---|---|
| API key in logs | Symbol-keyed private storage; JSON.stringify(client) and util.inspect(client) return [REDACTED] |
| API key in browser bundle | Constructor throws if window is defined (escape hatch: allowBrowser: true) |
| Webhook replay | Timestamp included in HMAC; 300s tolerance window; rejected if stale |
| Webhook signature tampering | crypto.timingSafeEqual on equal-length Buffers |
| MITM | HTTPS-only (except localhost for dev); TLS 1.2+ |
| Path traversal via IDs | Regex validation on session_id / patient_id |
| Prototype pollution | Native JSON.parse only |
| Double-billing on retry | Auto Idempotency-Key, reused across attempts |
For corporate proxies with custom root CAs, set NODE_EXTRA_CA_CERTS=path/to/ca.pem. The SDK does not expose a flag to disable certificate validation.
| Runtime | Supported | Notes |
|---|---|---|
| Node.js 18.17+ | ✅ | Primary target |
| Node.js 20+ | ✅ | Recommended |
| Bun 1.0+ | ✅ | Best-effort |
| Deno 1.40+ | ✅ | Best-effort |
| Cloudflare Workers | ✅ | Use @nessyapi/sdk/webhook for receivers |
| Browser | ❌ | API keys must stay on the server |
docs/ERROR_CODES.md — every error class, status code, and server errorCode with how to fix it.docs/TROUBLESHOOTING.md — common pitfalls (webhook signature mismatches, rate limiting, timeouts, browser refusal, etc.).examples/ — runnable snippets for Express, Fastify, Next.js App Router, Hono, Cloudflare Workers, AWS Lambda, plus pagination and error-handling patterns.@param descriptions, cost annotation, and @throws pointers. Your editor should show all of it on hover.MIT © HealthyNess.cz
Please report security issues to security@healthyness.cz or via GitHub Security Advisories. We disclose responsibly within 90 days.