Quickstart · TypeScript / JavaScript · ~5 min
TypeScript / JavaScript SDK
Behavioral intelligence for any JS runtime — Node 18+, Deno, Bun, and modern browsers. Ships full TypeScript types and a one-line wrap for any of 20+ providers (OpenAI, Anthropic, Gemini, Groq, Ollama, Bedrock, and more).
Just want monitoring? Lead with observe()
The fastest path to a signal is the one-line
observe() call — state + risk, no model config, never blocks. The TS SDK below leads with process() (Humane generates the reply); for the observe-only loop use the Python SDK or the raw /observe endpoint.1. Install
bash
npm i @humane-ai/sdk
# or pnpm add @humane-ai/sdk
# or bun add @humane-ai/sdk2. Your first call
typescript
import { HumaneClient } from "@humane-ai/sdk";
const client = new HumaneClient({ apiKey: process.env.HUMANE_KEY! });
const r = await client.process({
userId: "u_8f2",
message: "honestly I don't know why I keep trying",
});
console.log(r.response); // AI response shaped by behavioral context
console.log(r.user.mood); // 0.28
console.log(r.safety.action); // "PROCEED" | "HOLD" | "BLOCK"
console.log(r.analysis?.reasons); // ["'struggling' → sadness (negative)"]process() needs an LLM configured
process() generates a reply, so the tenant must have finished onboarding. If not, the call throws a HumaneError wrapping the 409 (code: "onboarding_required"). Catch it and fall back to your own model or the /observe endpoint, which never requires onboarding.3. One-line wrap for OpenAI
Drop-in wrapper — every chat.completions.create() call now carries a .humane property with the full behavioral payload.
typescript
import OpenAI from "openai";
import { wrap } from "@humane-ai/sdk";
const client = wrap(new OpenAI(), { humaneKey: process.env.HUMANE_KEY! });
const resp = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "I'm anxious" }],
user: "u_8f2",
});
console.log(resp.choices[0].message.content);
console.log((resp as any).humane.user.mood); // 0.31
console.log((resp as any).humane.safety.action); // PROCEED | BLOCKBlocked messages never hit OpenAI
If Humane's safety gate returns
BLOCK, the wrapper short-circuits before calling OpenAI — no token spend on blocked messages.4. Streaming
typescript
for await (const ev of client.stream({ userId: "u_8f2", message: "tell me more" })) {
if (ev.type === "metadata") {
console.log("mood:", ev.data.user.mood, "safety:", ev.data.safety.action);
} else if (ev.type === "token") {
process.stdout.write(ev.data.content);
} else if (ev.type === "final") {
// Full text + metadata — reconcile state here.
}
}5. Verifying webhook signatures
Webhook receivers MUST verify the X-Humane-Signatureheader. Import the helper from the sub-path @humane-ai/sdk/webhook:
typescript
import express from "express";
import { verifyWebhookSignature } from "@humane-ai/sdk/webhook";
const app = express();
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.header("X-Humane-Signature");
if (!verifyWebhookSignature(process.env.HUMANE_WEBHOOK_SECRET!, req.body, sig)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString("utf8"));
// handle event ...
res.sendStatus(200);
});6. Safe retries
typescript
await client.process({
userId: "u_8f2",
message: "hi",
idempotencyKey: "req_8f3c9a1b4d", // same key + same body → cached reply
});7. Durable history
typescript
const history = await client.getHistory("u_8f2", 200);
// → [{ role, content, timestamp }, ...] from the platform DB
await client.clearHistory("u_8f2"); // GDPR right-to-erasure8. Human takeover
The takeover lifecycle (operator steps into a live conversation, then hands back to the AI) is exposed in the Python SDK and over plain REST. From TypeScript, call the same four endpoints with fetch and your X-API-Key:
typescript
const base = "https://humaneai.vaarak.com/api/sdk";
const headers = { "X-API-Key": process.env.HUMANE_KEY!, "Content-Type": "application/json" };
// Open a session
await fetch(`${base}/takeover/u_8f2`, {
method: "POST", headers,
body: JSON.stringify({ operator_id: "op_1", operator_name: "Dana" }),
});
// Send an operator-authored turn (the AI is suppressed while active)
await fetch(`${base}/takeover/u_8f2/messages`, {
method: "POST", headers,
body: JSON.stringify({ body: "Hey, it's Dana — I'm here." }),
});
// Hand back to the AI
await fetch(`${base}/takeover/u_8f2/release`, { method: "POST", headers, body: "{}" });