Use the Anthropic Node SDK with a custom baseURL.
The Anthropic Node SDK (@anthropic-ai/sdk) defaults to https://api.anthropic.com. To route it through MonkeyAI, pass baseURL: to the client constructor or export ANTHROPIC_BASE_URL. The SDK then sends x-api-key plus anthropic-version: 2023-06-01 to /v1/messages. This guide verifies that path against production on 2026-08-09 and walks through a real client.messages.create() call.
What was actually verified on production?
Six probes were sent to https://monkeytheme.com/v1/messages with a deliberately invalid test key, on 2026-08-09. Every probe returned HTTP 401 with a structured JSON body and the x-request-id header in the response. The sub2api backend log shows ingress_reject_reason=invalid_api_key with latency_ms=0-3, meaning the relay gates on auth before any upstream call. The Node SDK behaviour mirrors the Python SDK because both implement the same wire protocol.
| Probe | Headers | Response | What it confirms |
|---|---|---|---|
POST /v1/messages | x-api-key only | 401 INVALID_API_KEY | SDK-default auth path works without anthropic-version |
POST /v1/messages | x-api-key + anthropic-version: 2023-06-01 | 401 INVALID_API_KEY | SDK-style dual-header request reaches the relay intact |
POST /v1/messages | Authorization: Bearer *** | 401 INVALID_API_KEY | Bearer also accepted on Messages, same as OpenAI routes |
GET /v1/models | x-api-key | 401 INVALID_API_KEY | Models endpoint also gates on auth |
GET /v1/models | anthropic-version alone | 401 API_KEY_REQUIRED | anthropic-version is NOT used as an auth header |
POST /v1/messages | x-api-key + tools + stream: true | 401 INVALID_API_KEY | Auth gate runs before schema parse or stream branch |
Install and configure the client
The Anthropic Node SDK is on npm as @anthropic-ai/sdk. It accepts baseURL, apiKey, and authToken on the constructor. The README documents ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY as environment fallbacks.
npm install @anthropic-ai/sdk export ANTHROPIC_BASE_URL="https://monkeytheme.com" export ANTHROPIC_API_KEY="***"
Do not commit the key into source. process.env is fine for the snippet below; for production prefer a secrets manager or a .env.local file outside version control.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
baseURL: process.env.ANTHROPIC_BASE_URL,
});
baseURL as the API root without a trailing /v1 or /messages. The SDK builds the full URL itself: {baseURL}/v1/messages. Appending /v1 by hand causes a 404 because the resulting path becomes /v1/v1/messages.Verify the key before sending real prompts
The Anthropic SDK does not expose a models.list() helper. To confirm a key is alive, send a single-token client.messages.create() call with max_tokens: 1. If the key is bad you get a 401 with a request ID; if the key is good you get a 1-token Claude reply with a model field that confirms the upstream model name.
const resp = await client.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1,
messages: [{ role: "user", content: "Reply with OK" }],
});
console.log(resp.model, resp.stop_reason, resp.usage);
If resp.model prints a Claude model name visible to your account, the SDK wiring is correct. The Anthropic SDK pins anthropic-version to 2023-06-01 on every request; you do not need to set it yourself.
How should errors be handled?
The SDK throws APIError for non-2xx responses and AnthropicError for other SDK-side issues. Both expose .status and .headers. Log the request ID from the error headers; do not log the API key.
try {
const resp = await client.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 64,
messages: [{ role: "user", content: "hi" }],
});
} catch (err) {
if (err.status) {
const reqId = err.headers?.get?.("x-request-id");
console.error("HTTP", err.status, "request_id", reqId);
} else {
console.error("network/TLS:", err.cause ?? err.message);
}
throw err;
}
A 401 with code=INVALID_API_KEY from MonkeyAI is the same code the OpenAI route returns. The relay does not distinguish between Anthropic and OpenAI keys at this layer; both are MonkeyAI keys, and they live in the same backend.
Streaming, tools and retries
Streaming on /v1/messages uses Server-Sent Events. The Anthropic SDK exposes a stream helper via client.messages.stream(...). Retries on 429 and 5xx are on by default; set maxRetries: 0 while debugging so a transient 429 does not silently mask the real failure.
const stream = client.messages.stream({
model: "claude-3-5-sonnet-20241022",
max_tokens: 256,
messages: [{ role: "user", content: "Summarise this in one sentence." }],
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
const final = await stream.finalMessage();
Tools work the same way they do against api.anthropic.com. If a tool_use block comes back, the SDK parses it into resp.content. If you instead see a 400 from upstream after a 200 from the relay, the upstream account likely rejected the tool schema, not MonkeyAI.
Common configuration mistakes
- Setting
baseURLtohttps://monkeytheme.com/v1with the SDK on top. The SDK already adds/v1; the resulting path becomes/v1/v1/messagesand nginx returns 404. - Setting
apiKeyto the OpenAI-stylesk-***value and then wondering why Bearer works but the SDK still 401s. The Anthropic SDK sendsx-api-key; the relay accepts it on/v1/messages, but the same string is the MonkeyAI account key. - Stripping
anthropic-versionwith a fetch interceptor. The SDK sets it; removing it does not help and breaks upstream compatibility checks. - Calling
client.models.list()on the Anthropic client. There is no such method on the Anthropic SDK; usefetchagainstGET /v1/modelsdirectly. - Treating a 401 INVALID_API_KEY as evidence of upstream outage. The 401 came from the relay's auth gate, not from the upstream account. Check the API key first.
Sources
FAQ
What baseURL should I pass?
Pass baseURL: https://monkeytheme.com. The SDK appends /v1/messages.
Does the SDK send Authorization Bearer?
No. @anthropic-ai/sdk sends x-api-key by default. MonkeyAI also accepts Authorization: Bearer on /v1/messages.
Does anthropic-version matter?
The Anthropic SDK sets it on every request. MonkeyAI does not require it but passes it upstream; stripping it on the client side breaks upstream, not MonkeyAI.
Does models.list exist on the Node SDK?
No. The Anthropic SDK does not expose a models list helper. Use GET /v1/models via fetch or undici instead.
Does 401 prove the SDK wiring is correct?
No. 401 INVALID_API_KEY proves the request reached the relay and auth was rejected. Run a real client.messages.create() with your own key to verify generation.
Related guides
OpenAI Node SDK
The OpenAI-compatible equivalent for /v1/chat/completions and /v1/responses.
Open page