Skip to main content

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 openai

Send a request​

chat.mjs
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.mjs

You see a request ID and Hello. In a server, create one client and reuse it.

The same request with native fetch
chat-fetch.mjs
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:

ResponseWhat it meansWhat to do
200 with x-verdictan-verdict: ESCALATEA 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_violationA policy blocked the request.Show a refusal. Other 400 codes are malformed requests.
401 or 403The token is missing, revoked, or not allowed.Check the application token and its access.
402A budget or balance limit.Check the error code.
429A rate limit.Honor Retry-After if present, within your retry limit.
Timeout or 5xxThe 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​