Axemere Gateway — TypeScript / Node.js SDK

Add governance, cost controls, and an audit trail to every AI call, without changing your application code. The Node SDK is a drop-in replacement for the OpenAI, Anthropic, Google Gemini, and LangChain clients.

GitHub → Axemere-LLC/axemere-node


Packages

PackageInstallUse when
@axemere/gatewaynpm install @axemere/gatewayDirect gateway API; any provider
@axemere/gateway-openainpm install @axemere/gateway-openaiYou use the openai SDK
@axemere/gateway-anthropicnpm install @axemere/gateway-anthropicYou use @anthropic-ai/sdk
@axemere/gateway-googlenpm install @axemere/gateway-googleYou use @google/generative-ai
@axemere/gateway-langchainnpm install @axemere/gateway-langchainYou use LangChain.js

Requires Node.js 18+ (native fetch required).


Quick start

npm install @axemere/gateway-openai
// Before
import OpenAI from "openai";
const client = new OpenAI();

// After — swap the import, nothing else changes
import { openaiClient } from "@axemere/gateway-openai";
const client = openaiClient(); // reads AXEMERE_GATEWAY_URL and AXEMERE_GATEWAY_TOKEN from env

const response = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content);

Configuration

AiGatewayConfig

import { AiGatewayConfig } from "@axemere/gateway"

All fields read from environment variables at construction time. Pass an options object to override individual values.

const config = new AiGatewayConfig({
  gateway_url: "http://localhost:7080",
  gateway_token: "tok_...",
  workload_id: "wl_my_app",
});
FieldTypeEnv varDefaultDescription
gateway_urlstringAXEMERE_GATEWAY_URLhttp://localhost:7080Gateway base URL
gateway_tokenstring | undefinedAXEMERE_GATEWAY_TOKENBearer token
default_providerstring | undefinedAXEMERE_PROVIDERProvider used when none is passed to execute()
default_modelstring | undefinedAXEMERE_MODELModel used when none is passed to execute()
workload_idstring | undefinedAXEMERE_WORKLOAD_IDWorkload ID for attribution and policy matching
project_idstring | undefinedAXEMERE_PROJECT_IDProject ID for budget controls and reporting
account_idstring | undefinedAXEMERE_ACCOUNT_IDAccount ID for attribution
customer_idstring | undefinedAXEMERE_CUSTOMER_IDCustomer ID for attribution
labelsRecord<string, string> | undefinedArbitrary key-value labels attached to every request
provider_api_keystring | undefinedAXEMERE_PROVIDER_API_KEYBYOK provider key forwarded to upstream
timeoutnumberAXEMERE_TIMEOUT_SECONDS120HTTP timeout in seconds

Methods

config.proxyUrl(provider: string): string Returns the proxy base URL for the given provider. Use as the baseURL for an existing provider SDK (see Proxy mode).


AiGatewayClient

import { AiGatewayClient } from "@axemere/gateway"

Framework-independent client for the gateway's explicit action API. Use this when you want full control or are not using an OpenAI/Anthropic/Google SDK.

import { AiGatewayClient, AiGatewayConfig } from "@axemere/gateway";

const client = new AiGatewayClient(new AiGatewayConfig());

client.execute(params): Promise<ActionResult>

const result = await client.execute({
  provider: "openai",
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello" }],
  max_tokens: 512,
});
console.log(result.content);
console.log(`Cost: $${result.metering?.cost_usd}`);

ExecuteParams

FieldTypeDefaultDescription
providerstringProvider name ("openai", "anthropic", "google", etc.)
modelstringModel identifier
messagesMessage[]OpenAI-format message list
max_tokensnumber256Maximum tokens to generate. Anthropic requires this field; the SDK always sends it
temperaturenumberSampling temperature
[key: string]unknownAny additional provider parameters

client.stream(params): AsyncIterable<StreamChunk>

for await (const chunk of client.stream({
  provider: "anthropic",
  model: "claude-haiku-4-5-20251001",
  messages: [{ role: "user", content: "Count to 5" }],
})) {
  if (!chunk.is_final) {
    process.stdout.write(chunk.content);
  } else {
    console.log(`\nRecord: ${chunk.record_id}`);
  }
}

Accepts the same parameters as execute(). Yields StreamChunk objects; the final chunk has is_final: true and carries record_id and metering.


Response types

ActionResult

FieldTypeDescription
contentstringAssistant text extracted from the provider response
record_idstringGateway record ID for audit lookup
meteringMetering | undefinedToken counts and cost
decisionstringGateway policy decision ("allow" or "deny")
provider_responseunknownRaw provider response body

StreamChunk

FieldTypeDescription
contentstringText delta (empty on final chunk)
is_finalbooleantrue on the last chunk
record_idstring | undefinedPopulated on the final chunk
meteringMetering | undefinedPopulated on the final chunk

Metering

FieldTypeDescription
cost_usdstringTotal request cost in USD
tokens_innumberInput token count
tokens_outnumberOutput token count
cache_hit_tokensnumber | undefinedCached prompt tokens (Anthropic)
reasoning_tokensnumber | undefinedReasoning tokens (o-series models)
cost_breakdownCostBreakdownItem[]Per-tier cost line items

Errors

All errors inherit from GatewayError.

import { GatewayError, PolicyDeniedError, RateLimitError } from "@axemere/gateway";

try {
  const result = await client.execute({ ... });
} catch (e) {
  if (e instanceof PolicyDeniedError) {
    console.log(`Denied: ${e.reason}`);
  } else if (e instanceof RateLimitError) {
    console.log("Rate limited, back off and retry");
  } else if (e instanceof GatewayError) {
    console.log(`Gateway error: ${e.message}`);
  }
}
ClassWhen thrownExtra fields
GatewayErrorBase class; network failure, non-2xx HTTP, malformed responsemessage
PolicyDeniedErrorRequest denied by gateway policy (HTTP 403)reason, trace
RateLimitErrorRate limit or quota exceeded (HTTP 429)

Wrapper packages

@axemere/gateway-openai

import { openaiClient } from "@axemere/gateway-openai"

Returns a pre-configured OpenAI instance from the official openai npm package. All methods, types, and streaming behaviour are identical.

import { openaiClient } from "@axemere/gateway-openai";

const openai = openaiClient();                          // uses AXEMERE_* env vars
const openai = openaiClient(new AiGatewayConfig(...)); // explicit config

// Use exactly as you would openai.OpenAI
const stream = openai.chat.completions.stream({ ... });

openaiClient(config?: AiGatewayConfig): OpenAI

@axemere/gateway-anthropic

import { anthropicClient } from "@axemere/gateway-anthropic"

Returns a pre-configured Anthropic instance from @anthropic-ai/sdk.

import { anthropicClient } from "@axemere/gateway-anthropic";

const anthropic = anthropicClient();

const message = await anthropic.messages.create({
  model: "claude-haiku-4-5-20251001",
  max_tokens: 256,
  messages: [{ role: "user", content: "Hello" }],
});

anthropicClient(config?: AiGatewayConfig): Anthropic

@axemere/gateway-google

import { genaiClient } from "@axemere/gateway-google"

Returns a GoogleGenerativeAI-compatible instance. Gateway auth headers and base URL are injected automatically into every getGenerativeModel() call: no requestOptions needed.

import { genaiClient } from "@axemere/gateway-google";

const ai = genaiClient();
const model = ai.getGenerativeModel({ model: "gemini-1.5-flash" });
const result = await model.generateContent("Hello");
console.log(result.response.text());

genaiClient(config?: AiGatewayConfig): GoogleGenerativeAI

@axemere/gateway-langchain

import { ChatAiGateway } from "@axemere/gateway-langchain"

A LangChain BaseChatModel that routes completions through the gateway action API. Supports 12 providers and both buffered and streaming responses.

Also re-exports aiGatewayOpenAIClient and aiGatewayAnthropicClient for proxy-mode use within LangChain workflows.

import { ChatAiGateway } from "@axemere/gateway-langchain";

const llm = new ChatAiGateway({
  provider: "openai",
  model: "gpt-4o-mini",
  maxTokens: 512,
});
const response = await llm.invoke("Explain monads in one sentence.");
console.log(response.content);

ChatAiGateway constructor fields

FieldTypeDefaultDescription
providerstringProvider name. Must be one of the supported providers below
modelstringModel identifier
configAiGatewayConfigenv varsGateway connection config
maxTokensnumber256Maximum tokens to generate
temperaturenumber | undefinedSampling temperature
workloadIdstring | undefinedOverrides config.workload_id for this model
projectIdstring | undefinedOverrides config.project_id for this model
accountIdstring | undefinedOverrides config.account_id
customerIdstring | undefinedOverrides config.customer_id
labelsRecord<string, string> | undefinedMerged on top of config.labels

Supported providers: openai, anthropic, mistral, google, xai, deepseek, groq, together, fireworks, perplexity, openrouter, cohere

Streaming

for await (const chunk of await llm.stream("Count to 5")) {
  process.stdout.write(chunk.content as string);
}

Full example → Axemere-LLC/langchain-gateway-node-demo — a LangChain.js multi-agent code review pipeline built with the Axemere Gateway.


Proxy mode

Use config.proxyUrl() to point any existing provider SDK at the gateway without changing packages.

import OpenAI from "openai";
import { AiGatewayConfig } from "@axemere/gateway";

const config = new AiGatewayConfig();
const client = new OpenAI({
  baseURL: config.proxyUrl("openai"),
  apiKey: "placeholder", // gateway handles auth
});

Note: The gateway token is embedded in the proxy URL path (/k/{token}). URLs appear in access logs. Prefer the wrapper packages or AiGatewayClient: they send the token in the Authorization header.


Get a gateway

  • Free tier — self-hosted, no account required → Install
  • Self-Hosted — team features + control plane → Get started
  • Managed — fully hosted, zero-ops → Get started