OpenAI and Anthropic SDKs
OpenAI, Anthropic
The raw settings for your own code. Any client that accepts a base URL and an API key connects this way.
Actualyze speaks the OpenAI API (Chat Completions, Responses and model listing) and the Anthropic Messages API. An application that already uses either SDK switches by changing three settings: the base URL, the API key, and the model name.
Before you start
- An Actualyze API key with the inference scope and read-write permission. Workspace Admins, Members and FinOps Managers who belong to a team can create one: in the Actualyze app, select API Keys under ADMIN in the sidebar (every role that can hold keys sees this page, not only admins), then select Create API Key. Keep the default Read & Write permission. Every call made with the key is charged to the budget it was created with.
- The ID of a model your key can call: a catalog model ID or a Virtual Model slug. List them with the key itself.
Install
pip install openai anthropic
npm install openai @anthropic-ai/sdkInstall only the SDKs you use. Both Python SDKs need Python 3.10 or later. The current openai package for Node.js (v7) needs Node.js 22 or later; @anthropic-ai/ needs Node.js 20 or later.
Connect to Actualyze
| Client speaks | Base URL |
|---|---|
| OpenAI Chat Completions, Responses | https:// |
| Anthropic Messages | https:// |
The Anthropic base URL has no /: Anthropic SDKs append / themselves.
The examples read the key from an environment variable:
export ACTUALYZE_API_KEY="<your-actualyze-api-key>"OpenAI SDK for Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://app.actualyze.ai/openai/v1",
api_key=os.environ["ACTUALYZE_API_KEY"],
)
response = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)OpenAI SDK for Node.js
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://app.actualyze.ai/openai/v1',
apiKey: process.env.ACTUALYZE_API_KEY,
});
const response = await client.chat.completions.create({
model: '<model-id>',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(response.choices[0].message.content);Both OpenAI SDKs also read OPENAI_ and OPENAI_ from the environment, so an existing application can switch without a code change. Streaming, tool calling and the Responses API (client.responses.create) work through the same base URL. Actualyze handles Responses requests statelessly: send the whole conversation in input each turn, and leave out previous_, conversation, background: true and store: true.
Anthropic SDK for Python
import os
import anthropic
client = anthropic.Anthropic(
base_url="https://app.actualyze.ai/anthropic",
api_key=os.environ["ACTUALYZE_API_KEY"],
)
message = client.messages.create(
model="<model-id>",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)The SDK's usual api_ option works as is: the Anthropic SDKs send the key in an x-api-key header, and Actualyze accepts it there as well as in Authorization: Bearer. The SDKs' auth_ (authToken in Node.js) option, which sends Authorization: Bearer, works too. The Node.js SDK takes the same options as baseURL and apiKey, and both SDKs read ANTHROPIC_, ANTHROPIC_ and ANTHROPIC_ from the environment. When a request carries both headers, Actualyze uses Authorization: Bearer.
curl
curl https://app.actualyze.ai/openai/v1/chat/completions \
-H "Authorization: Bearer $ACTUALYZE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "<model-id>", "messages": [{"role": "user", "content": "Hello"}], "stream": true}'Choose a model
The model field takes a catalog model ID or a Virtual Model slug, written exactly as Actualyze lists it, without adding a provider/ prefix of your own. A Virtual Model is a stable name your administrators point at one or more models, so your code keeps the same string while the models behind it change.
Use a model your workspace serves over the protocol your client speaks. An OpenAI-style client calling a model that only speaks the Anthropic protocol, or the reverse, isn't supported.
List the models your key can call
curl https://app.actualyze.ai/openai/v1/models \
-H "Authorization: Bearer $ACTUALYZE_API_KEY"The list is computed for the key: every model in it is one the key can call. An administrator can hide a catalog model from the list while leaving it callable by name. Each entry also carries context_, max_ (both null when unknown), a capabilities block (tool_, vision, thinking, structured_ and more), modalities, and, when Actualyze can price the model, a cost block in US dollars per million tokens, so a client can configure itself. The Anthropic-shaped list is at https://.
Troubleshooting
402: a spend limit stopped the request before any model ran, so nothing was charged. The message says which limit: usually the key's budget, or the key's own monthly limit.401: the key was rejected. It may be mistyped, revoked or expired, lack the inference scope, or belong to someone who is no longer on the key's team.403: the request was refused. If the message says read-write permission is required, the key is read-only; create a read-write key. Otherwise your team can't use the model you asked for; check its access in the Model Catalog or pick another.402sayingBudget required: the key's budget was deleted, archived or retired, or you no longer have access to it. Create a new key bound to a budget you can use.- Requests fail right after setup: check the base URL. OpenAI clients need
/; Anthropic clients needopenai/ v1 /with nothing after it.anthropic 404model not found: the model ID doesn't match any catalog model or Virtual Model in your workspace. Check it against the model list above.401from the Anthropic SDK for Node.js although the key in your code is right: anANTHROPIC_left in the environment (from a Claude Code setup, for example) is sent alongside it asAUTH_ TOKEN Authorization: Bearer, and Actualyze uses that header. Unset the variable or set it to the same key.503from the Anthropic endpoint saying the model can't serve Anthropic (Claude Code) requests: the model has no route that speaks the Anthropic protocol. The message names Claude Code but applies to every Anthropic client. Pick a model that does.400from the Responses API namingprevious_,response_ id conversation,storeorbackground: Actualyze doesn't store responses. Send the full conversation ininput, and leavestoreunset orfalse.400from the Responses API saying the request requires a same-protocol OpenAI Responses endpoint: the request carries reasoning or built-in tool state, and the model isn't served over the Responses API. Pick a model that is, or drop the reasoning or built-in tools.400about theX-Actualyze-Request-Metadataheader: the header isn't a JSON object of 1 to 16 string pairs, a key or value is longer than 256 characters, or it uses a character outside letters, digits, spaces and- : _. See Reference.@ $ # = / + , .
Reference
-
Requests you cancel mid-stream still draw down the usage they consumed.
-
Actualyze recognizes the OpenAI and Anthropic SDKs for Python and Node.js, LangChain and curl from the request's
User-Agent, and shows the client in the Client column of the Observability Activity tab. A client that replaces the SDK'sUser-Agentshows as Other. -
The optional
X-Actualyze-Request-Metadataheader attaches agent and run details to a request: a JSON object of 1 to 16 string pairs, each key and value up to 256 characters. Actualyze understands the keysagent,run_,id frameworkanduser, and stores any other key with them. The chat, Responses and Anthropic Messages endpoints read it. Every SDK here can send it on all requests with the client'sdefault_(Python) orheaders defaultHeaders(Node.js) option:client = OpenAI( base_url="https://app.actualyze.ai/openai/v1", api_key=os.environ["ACTUALYZE_API_KEY"], default_headers={ "X-Actualyze-Request-Metadata": '{"agent": "invoice-bot", "run_id": "run-42"}' }, ) -
SDK documentation: OpenAI libraries and Anthropic client SDKs.