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
| Package | Install | Use when |
|---|---|---|
@axemere/gateway | npm install @axemere/gateway | Direct gateway API; any provider |
@axemere/gateway-openai | npm install @axemere/gateway-openai | You use the openai SDK |
@axemere/gateway-anthropic | npm install @axemere/gateway-anthropic | You use @anthropic-ai/sdk |
@axemere/gateway-google | npm install @axemere/gateway-google | You use @google/generative-ai |
@axemere/gateway-langchain | npm install @axemere/gateway-langchain | You 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", });
| Field | Type | Env var | Default | Description |
|---|---|---|---|---|
gateway_url | string | AXEMERE_GATEWAY_URL | http://localhost:7080 | Gateway base URL |
gateway_token | string | undefined | AXEMERE_GATEWAY_TOKEN | — | Bearer token |
default_provider | string | undefined | AXEMERE_PROVIDER | — | Provider used when none is passed to execute() |
default_model | string | undefined | AXEMERE_MODEL | — | Model used when none is passed to execute() |
workload_id | string | undefined | AXEMERE_WORKLOAD_ID | — | Workload ID for attribution and policy matching |
project_id | string | undefined | AXEMERE_PROJECT_ID | — | Project ID for budget controls and reporting |
account_id | string | undefined | AXEMERE_ACCOUNT_ID | — | Account ID for attribution |
customer_id | string | undefined | AXEMERE_CUSTOMER_ID | — | Customer ID for attribution |
labels | Record<string, string> | undefined | — | — | Arbitrary key-value labels attached to every request |
provider_api_key | string | undefined | AXEMERE_PROVIDER_API_KEY | — | BYOK provider key forwarded to upstream |
timeout | number | AXEMERE_TIMEOUT_SECONDS | 120 | HTTP 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
| Field | Type | Default | Description |
|---|---|---|---|
provider | string | — | Provider name ("openai", "anthropic", "google", etc.) |
model | string | — | Model identifier |
messages | Message[] | — | OpenAI-format message list |
max_tokens | number | 256 | Maximum tokens to generate. Anthropic requires this field; the SDK always sends it |
temperature | number | — | Sampling temperature |
[key: string] | unknown | — | Any 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
| Field | Type | Description |
|---|---|---|
content | string | Assistant text extracted from the provider response |
record_id | string | Gateway record ID for audit lookup |
metering | Metering | undefined | Token counts and cost |
decision | string | Gateway policy decision ("allow" or "deny") |
provider_response | unknown | Raw provider response body |
StreamChunk
| Field | Type | Description |
|---|---|---|
content | string | Text delta (empty on final chunk) |
is_final | boolean | true on the last chunk |
record_id | string | undefined | Populated on the final chunk |
metering | Metering | undefined | Populated on the final chunk |
Metering
| Field | Type | Description |
|---|---|---|
cost_usd | string | Total request cost in USD |
tokens_in | number | Input token count |
tokens_out | number | Output token count |
cache_hit_tokens | number | undefined | Cached prompt tokens (Anthropic) |
reasoning_tokens | number | undefined | Reasoning tokens (o-series models) |
cost_breakdown | CostBreakdownItem[] | 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}`); } }
| Class | When thrown | Extra fields |
|---|---|---|
GatewayError | Base class; network failure, non-2xx HTTP, malformed response | message |
PolicyDeniedError | Request denied by gateway policy (HTTP 403) | reason, trace |
RateLimitError | Rate 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
| Field | Type | Default | Description |
|---|---|---|---|
provider | string | — | Provider name. Must be one of the supported providers below |
model | string | — | Model identifier |
config | AiGatewayConfig | env vars | Gateway connection config |
maxTokens | number | 256 | Maximum tokens to generate |
temperature | number | undefined | — | Sampling temperature |
workloadId | string | undefined | — | Overrides config.workload_id for this model |
projectId | string | undefined | — | Overrides config.project_id for this model |
accountId | string | undefined | — | Overrides config.account_id |
customerId | string | undefined | — | Overrides config.customer_id |
labels | Record<string, string> | undefined | — | Merged 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 orAiGatewayClient: they send the token in theAuthorizationheader.
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