SDKs & API
JavaScript / TypeScript
Node.js, Deno, and Bun with typed results and framework snippets.
What this guide does not cover
- Server-side only. The SDK must never run in a browser bundle, because that would expose your API key.
- Does not cover React, Vue, or any client framework directly — call your own backend route from the client.
Step 1: Install the package
Open your terminal and run:
npm install @soterai/coredeno add npm:@soterai/core orbun add @soterai/core.Step 2: Configure environment
Add these to your .env file:
# 📁 .env file - server-side only!
SOTER_API_KEY=ck_live_your_key_here
SOTER_PROJECT_ID= # optional - for multi-project setupsNEXT_PUBLIC_. The SDK warns if it detects a browser environment. Always call SoterAI from your backend server.Step 3: Basic usage (guarding user input)
Protect a single user message before it reaches your AI model:
1import { Soter } from "@soterai/core";
2
3// Initialize once - reuse for all requests
4const soter = new Soter({
5 apiKey: process.env.SOTER_API_KEY,
6 projectId: process.env.SOTER_PROJECT_ID, // optional
7 timeoutMs: 5000, // 5 second timeout
8});
9
10// Protect a single user input before it reaches your LLM
11const result = await soter.protect({
12 input: userMessage,
13 context: { userId: "user_123", sessionId: "session_123" },
14});
15
16if (!result.allowed) {
17 // 🛑 Input is dangerous! Don't call the LLM
18 console.log("Blocked by Soter:", result.reason);
19 return { blocked: true, reason: result.reason };
20}
21
22// ✅ Safe to proceed
23return callYourLLM(result.safeText ?? userMessage);Decision helpers
The SDK provides convenient helpers for common decision patterns:
soter.isAllowed(result); // ✅ True = safe to forward
soter.shouldBlock(result); // 🛑 True = stop (BLOCK or HUMAN_REVIEW)
soter.getSafeText(result, fallback); // Returns safe/redacted text or fallbackOne-call conversation helper
Runs input guard → LLM call → output guard in a single call:
// One-call helper: input guard → LLM → output guard
const { reply, blocked } = await soter.guardConversation({
input: userMessage,
callLLM: async (safeInput) => myLLMCall(safeInput),
});
// reply = safe/redacted LLM response
// blocked = true if either guard blockedStep 4: Complete example with error handling
Here's a production-ready example that guards both input and output, handles errors, and decides whether to fail open or fail closed:
1import { Soter } from "@soterai/core";
2
3const soter = new Soter({
4 apiKey: process.env.SOTER_API_KEY,
5 projectId: process.env.SOTER_PROJECT_ID,
6 timeoutMs: 5000,
7 retries: 2, // auto-retry on transient errors
8});
9
10async function handleChatMessage(userMessage: string) {
11 try {
12 // 1. Guard user input
13 const inputResult = await soter.protect({
14 input: userMessage,
15 context: { sessionId: crypto.randomUUID() },
16 });
17
18 if (!inputResult.allowed) {
19 return { reply: inputResult.safeText ?? "Message blocked.", blocked: true };
20 }
21
22 // 2. Call your LLM with safe text
23 const safeInput = inputResult.safeText ?? userMessage;
24 const llmReply = await callYourLLM(safeInput);
25
26 // 3. Guard model output
27 const outputResult = await soter.guardOutput({ text: llmReply });
28
29 return {
30 reply: outputResult.safeText ?? outputResult.redactedText ?? llmReply,
31 blocked: !outputResult.allowed,
32 riskScore: outputResult.riskScore,
33 };
34 } catch (error) {
35 // Decide: fail open (allow) or fail closed (block)
36 console.error("SoterAI error:", error);
37 return { reply: "Service unavailable. Please try again.", blocked: false };
38 }
39}Framework integrations
Next.js
Use the createGuardedRoute helper from @soterai/core/next:
// One-line route handler
import { createGuardedRoute } from "@soterai/core/next";
export const runtime = "nodejs";
export const POST = createGuardedRoute({
apiKey: process.env.SOTER_API_KEY!,
callLLM: async (safeInput) => myLLMCall(safeInput),
});See the Next.js guide for more details.
Express.js
Use soterInputMiddleware and soterOutputMiddleware:
import { soterInputMiddleware, soterOutputMiddleware } from "@soterai/core/express";
app.post(
"/chat",
soterInputMiddleware({ apiKey: process.env.SOTER_API_KEY! }),
async (req, res) => {
// req.body.message is now safe/redacted
const reply = await callLLM(req.body.message);
res.json({ reply });
},
);Error handling
The SDK exports typed error classes for each failure mode:
import {
SoterError,
SoterAuthError,
SoterRateLimitError,
SoterValidationError,
SoterNetworkError,
} from "@soterai/core";
try {
await soter.guardInput({ text });
} catch (caught) {
if (caught instanceof SoterRateLimitError) {
// Retry after caught.retryAfter seconds
console.log(`Rate limited. Retry after ${caught.retryAfter}s`);
} else if (caught instanceof SoterAuthError) {
// 401/403 - check your API key
console.error("Auth error - check your API key");
} else if (caught instanceof SoterNetworkError) {
// Network timeout or connection refused
console.error("Network error - is your server reachable?");
} else if (caught instanceof SoterValidationError) {
// Bad request - check the payload shape
console.error("Validation error - check payload fields");
}
}retries: 2 in the constructor to auto-retry transient 5xx/network errors with exponential backoff.Security best practices
API key is never logged
Even with debug: true, the SDK never logs the API key or raw text.
Browser detection
The constructor warns if it detects a browser environment.
Safe error messages
Errors never include the API key in their messages.
Always guard output too
The input guard is not enough. Always guard the model output as well.
Set a timeout
Use timeoutMs: 5000 to prevent hanging requests.
Handle failures gracefully
Decide if your app should fail open (allow traffic) or fail closed (block traffic) when SoterAI is unreachable.
This reduces risk; it does not guarantee complete protection.