SDKs
Node.js Integration
Point the OpenAI client for Node.js at your Verdictan gateway, and your server's model calls go through your policies with no other code changes. This page sends one request, streams a reply, and handles the answers the gateway can give instead of a completion.
Before you start
- A running gateway from the quickstart, with its application token and a configured model.
- Node.js 20 or newer (
node --version).
Configure the client
Set three variables where your server runs:
export VERDICTAN_GATEWAY_URL="http://127.0.0.1:41002/v1"
export VERDICTAN_CLIENT_TOKEN="<application-token>"
export VERDICTAN_MODEL="<configured-model>"If the gateway runs on another host, use its base URL instead, still ending in /v1. The provider key stays on the gateway. Keep the application token on your server: never put it in a browser bundle or a public environment variable.
Install the client:
npm install openaiSend a request
import OpenAI from "openai";
function requiredEnv(name) {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
const model = requiredEnv("VERDICTAN_MODEL");
const client = new OpenAI({
baseURL: requiredEnv("VERDICTAN_GATEWAY_URL"),
apiKey: requiredEnv("VERDICTAN_CLIENT_TOKEN"),
maxRetries: 0,
timeout: 60_000,
});
try {
const { data, response } = await client.chat.completions.create({
model,
messages: [{ role: "user", content: "Reply with Hello." }],
}).withResponse();
console.log("Request ID:", response.headers.get("x-request-id"));
if (response.headers.get("x-verdictan-verdict") === "ESCALATE") {
console.log("Waiting for human review. Do not resend yet.");
} else {
const answer = data.choices[0]?.message.content;
if (!answer) throw new Error("No text answer. Inspect the request in Verdictan.");
console.log(answer);
}
} catch (error) {
if (error instanceof OpenAI.APIError) {
console.error("Request failed:", error.status, error.code ?? "unknown_error");
process.exitCode = 1;
} else {
throw error;
}
}node chat.mjsYou see a request ID and Hello. In a server, create one client and reuse it.
The same request with native fetch
const { VERDICTAN_GATEWAY_URL: base, VERDICTAN_CLIENT_TOKEN: token,
VERDICTAN_MODEL: model } = process.env;
if (!base || !token || !model) throw new Error("Set the three Verdictan variables.");
const response = await fetch(`${base.replace(/\/+$/, "")}/chat/completions`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({ model, messages: [{ role: "user", content: "Reply with Hello." }] }),
signal: AbortSignal.timeout(60_000),
});
const body = await response.json().catch(() => null);
console.log("Request ID:", response.headers.get("x-request-id"));
if (!response.ok) throw new Error(`HTTP ${response.status}: ${body?.error?.code ?? "unknown_error"}`);
if (response.headers.get("x-verdictan-verdict") === "ESCALATE") {
console.log("Waiting for human review.");
} else {
const answer = body?.choices?.[0]?.message?.content;
if (!answer) throw new Error("No text answer. Inspect the request in Verdictan.");
console.log(answer);
}Log the status, error code, and request ID, not the whole error body: it can contain request details.
Stream a response
Replace the try block with this loop, keeping the same client:
const stream = await client.chat.completions.create({
model,
messages: [{ role: "user", content: "Explain what a gateway does in two sentences." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta.content ?? "");
}
process.stdout.write("\n");With some policy chains, the gateway holds the whole answer and sends it at the end, so the stream arrives in one piece. An empty stream or a content_filter finish reason means a policy acted. See Streaming and SSE before you build a streaming UI.
Handle gateway decisions
Every response carries x-verdictan-verdict and x-verdictan-reason-code headers. Handle these cases:
| Response | What it means | What to do |
|---|---|---|
200 with x-verdictan-verdict: ESCALATE | A policy sent the request for human review. Content is null and finish_reason is content_filter. | Tell the user it is under review. Don't resend. |
400 with code content_policy_violation | A policy blocked the request. | Show a refusal. Other 400 codes are malformed requests. |
401 or 403 | The token is missing, revoked, or not allowed. | Check the application token and its access. |
402 | A budget or balance limit. | Check the error code. |
429 | A rate limit. | Honor Retry-After if present, within your retry limit. |
Timeout or 5xx | The gateway or provider failed. | Investigate before a retry: the provider may have already processed the request. |
A 200 without text is not always an escalation. It can also be a tool call or another output format. SDK retries are off in the example so that you decide when to retry. See Error Handling for the full list.
Find the request in Verdictan
Open History in your organization and search for the printed request ID. Request content is visible only when capture is on in the assigned configuration. Without capture, find the decision in the delivered gateway events.
Next steps
- Debugging Requests: trace a request that behaves unexpectedly.
- API Key Management: create and rotate application tokens.
- Runtime Request Families: the other APIs the gateway serves.