@atulo/sage
v0.1.0
Published
Official TypeScript / Node.js client for Sage by Atulo. Wrap every AI call in your healthcare product and prove it was HIPAA-defensible.
Maintainers
Readme
@atulo/sage
Official TypeScript / Node.js client for Sage by Atulo. Wrap every AI call inside your healthcare product and prove it was HIPAA-defensible.
Sage is the quiet observer that sits next to every AI call your team makes. It watches, it remembers, and when a regulator asks "how did your AI decide that," Sage answers, with receipts.
Install
npm install @atulo/sage
# or
bun add @atulo/sageQuick start
import { Sage } from "@atulo/sage";
const sage = new Sage({
apiKey: process.env.SAGE_API_KEY!,
baseUrl: process.env.SAGE_BASE_URL!, // e.g. "https://sage.your-domain.com"
});
const audit = await sage.log({
patient_id: "pt_a1b2c3",
context_tag: "diagnosis_summary",
ai_input: "Patient presents with persistent cough lasting 3 weeks.",
ai_output: "Recommend chest X-ray and CBC.",
consent_verified: true,
});
console.log(audit.audit_id); // -> "aud_8f3c2e91d6b4a702"API surface
new Sage({ apiKey, baseUrl?, fetchImpl?, timeoutMs? })
baseUrl is the URL of your Sage by Atulo deployment. Resolved in this order:
- The
baseUrlconstructor option, if passed. - The
SAGE_BASE_URLenv var, if set. - A loud placeholder (
https://your-sage-domain.example) so a missing configuration fails visibly instead of silently shipping requests to a dead host.
Set this to whatever you deploy to: https://sage.your-domain.com, http://localhost:3000 for local dev, a staging URL, or a self-hosted instance.
sage.log(input) — POST /v1/log
The core call. Wrap any AI completion you do in your product. Returns the sealed audit envelope including immutable_hash, consent_log, bias_check, and a Gemini-generated explanation.
sage.consent.upsert({ patient_id, consented, consent_scope?, revoked? }) — POST /v1/consent
Create or update the consent ledger entry for a patient. consent_scope is an array of allowed context_tag values. Pass revoked: true to flip the record off.
sage.consent.get(patient_id) — GET /v1/consent/:patient_id
Read current consent state.
sage.audit.list({ ...filters }) — GET /v1/audit
Paginated + filterable list. Filters: patient_id, context_tag, consent_status, bias_flag, from (ISO 8601), to, page, limit.
sage.audit.get(audit_id) — GET /v1/audit/:id
Single record by aud_* id.
sage.audit.export({ ...filters }) — GET /v1/audit/export
Returns the CSV body as a string (up to 10,000 rows per call).
sage.usage() — GET /v1/usage
Month-to-date usage + plan limit + remaining.
Sage.verifyWebhook({ body, signature, secret })
Static. Constant-time verification of the X-Atulo-Signature header on inbound webhook deliveries. Pass the raw request body (the same bytes we POSTed you, before any JSON parsing).
import { Sage } from "@atulo/sage";
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get("X-Atulo-Signature") ?? "";
const ok = Sage.verifyWebhook({
body: rawBody,
signature,
secret: process.env.SAGE_WEBHOOK_SECRET!,
});
if (!ok) return new Response("bad signature", { status: 401 });
const event = JSON.parse(rawBody);
// ... handle event.type
return new Response("ok");
}Errors: SageApiError
Every non-2xx response throws SageApiError with .status, .code (typed against the server's ApiErrorCode enum), and .message. Catch this to handle rate limits, missing consent, etc.
import { Sage, SageApiError } from "@atulo/sage";
try {
await sage.log({ ... });
} catch (e) {
if (e instanceof SageApiError && e.code === "rate_limit_exceeded") {
// back off and retry later
} else {
throw e;
}
}Browser / Edge
The client uses native fetch and is ESM-only. It works in Node 18+, Deno, Bun, Cloudflare Workers, and Vercel Edge Functions. Pass a custom fetchImpl if you need to inject one (testing, retries, etc.).
Sage.verifyWebhook uses node:crypto. If you receive webhooks in a Web-Crypto environment (Edge), use crypto.subtle.verify directly with the secret. See docs/INTEGRATION_GUIDE.md.
License
MIT
