Full example: an existing Anthropic integration, dev to production
Everything in Getting started as one concrete walkthrough, for a real project that already calls Anthropic directly and needs to go through FitGuard instead — the exact steps, in order, nothing skipped.
Step 1 — Install FitGuard once, outside your project
FitGuard is not a Python package. Do not pip install it, and it does not go in
requirements.txt or your virtualenv. It's a single Go binary that runs as its
own separate process — the same category of thing as Postgres or Redis, not a library your code imports. Install
it once on your machine (or server) with any method from Install, e.g.:
curl -fsSL https://raw.githubusercontent.com/Oluiy/ai-cost-guard/main/install.sh | sh
It's on your PATH now as the fitguard command, available from any project, the same way git or docker are.
Step 2 — Set up FitGuard and start it
fitguard init # choose "anthropic" when asked which providers to route through it fitguard run # leave this running in its own terminal / process
Copy the virtual key it prints (sk-guard-...) — you'll put it in your project's environment in Step 3, not in code.
Step 3 — Add the client and swap the code
Pick your stack below. Every language follows the same three-part structure — the dependency you add, the Before (direct to Anthropic), and the After (through FitGuard) — so switching languages doesn't change what you're looking for, only the syntax.
Add the dependency — inside your project's own virtualenv, the normal way you install any package:
source .venv/bin/activate pip install openai
And add it to whatever file tracks your dependencies:
# requirements.txt openai>=1.0.0
Before — direct to Anthropic:
from anthropic import Anthropic
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
answer = resp.content[0].text
After — through FitGuard:
from openai import OpenAI # the OpenAI package — correct, not a typo
client = OpenAI(
base_url=os.environ["FITGUARD_URL"], # e.g. "http://localhost:8787/v1" in dev
api_key=os.environ["FITGUARD_KEY"], # the sk-guard-... key from Step 2
)
resp = client.chat.completions.create(
model="claude-sonnet-5", # unchanged: same Claude model
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
answer = resp.choices[0].message.content
Add the dependency:
npm install openai
Before — direct to Anthropic (via @anthropic-ai/sdk):
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const resp = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
const answer = resp.content[0].text;
After — through FitGuard:
import OpenAI from "openai"; // the OpenAI package — correct, not @anthropic-ai/sdk
const client = new OpenAI({
baseURL: process.env.FITGUARD_URL, // e.g. "http://localhost:8787/v1" in dev
apiKey: process.env.FITGUARD_KEY, // the sk-guard-... key from Step 2
});
const resp = await client.chat.completions.create({
model: "claude-sonnet-5", // unchanged
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
const answer = resp.choices[0].message.content;
Add the dependency: none — HttpClient is built into .NET, no NuGet package needed either way.
Before — direct to Anthropic:
using System.Net.Http.Json;
var client = new HttpClient { BaseAddress = new Uri("https://api.anthropic.com/v1/") };
client.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"));
client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
var payload = new {
model = "claude-sonnet-5",
max_tokens = 1024,
messages = new[] { new { role = "user", content = prompt } }
};
var response = await client.PostAsJsonAsync("messages", payload);
var body = await response.Content.ReadAsStringAsync();
After — through FitGuard:
using System.Net.Http.Json;
var client = new HttpClient {
BaseAddress = new Uri(Environment.GetEnvironmentVariable("FITGUARD_URL") + "/")
};
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("FITGUARD_KEY"));
var payload = new {
model = "claude-sonnet-5", // unchanged
messages = new[] { new { role = "user", content = prompt } }
};
var response = await client.PostAsJsonAsync("chat/completions", payload);
var body = await response.Content.ReadAsStringAsync();
Add the dependency: none — java.net.http.HttpClient is built into the JDK (11+), no library needed either way.
Before — direct to Anthropic:
HttpClient client = HttpClient.newHttpClient();
String json = """
{"model":"claude-sonnet-5","max_tokens":1024,
"messages":[{"role":"user","content":"%s"}]}
""".formatted(prompt);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.anthropic.com/v1/messages"))
.header("x-api-key", System.getenv("ANTHROPIC_API_KEY"))
.header("anthropic-version", "2023-06-01")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
After — through FitGuard:
HttpClient client = HttpClient.newHttpClient();
String json = """
{"model":"claude-sonnet-5","messages":[{"role":"user","content":"%s"}]}
""".formatted(prompt); // model unchanged
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("FITGUARD_URL") + "/chat/completions"))
.header("Authorization", "Bearer " + System.getenv("FITGUARD_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Add the dependency: none — net/http is standard library, no module needed either way.
Before — direct to Anthropic:
body, _ := json.Marshal(map[string]any{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": []map[string]string{{"role": "user", "content": prompt}},
})
req, _ := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewReader(body))
req.Header.Set("x-api-key", os.Getenv("ANTHROPIC_API_KEY"))
req.Header.Set("anthropic-version", "2023-06-01")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
After — through FitGuard:
body, _ := json.Marshal(map[string]any{
"model": "claude-sonnet-5", // unchanged
"messages": []map[string]string{{"role": "user", "content": prompt}},
})
req, _ := http.NewRequest("POST", os.Getenv("FITGUARD_URL")+"/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("FITGUARD_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
Add the dependency:
# Cargo.toml
reqwest = { version = "0.12", features = ["json", "blocking"] }
serde_json = "1"
Before — direct to Anthropic:
let client = reqwest::blocking::Client::new();
let resp = client.post("https://api.anthropic.com/v1/messages")
.header("x-api-key", std::env::var("ANTHROPIC_API_KEY")?)
.header("anthropic-version", "2023-06-01")
.json(&serde_json::json!({
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": prompt}]
}))
.send()?;
After — through FitGuard:
let client = reqwest::blocking::Client::new();
let resp = client.post(format!("{}/chat/completions", std::env::var("FITGUARD_URL")?))
.bearer_auth(std::env::var("FITGUARD_KEY")?)
.json(&serde_json::json!({
"model": "claude-sonnet-5", // unchanged
"messages": [{"role": "user", "content": prompt}]
}))
.send()?;
Across every language, the shape of the change is identical: point the client at FitGuard's URL and virtual key instead of Anthropic's, use the OpenAI-shaped call instead of Anthropic's, keep the model name and everything else the same.
Step 4 — Run it in development
export FITGUARD_URL="http://localhost:8787/v1" export FITGUARD_KEY="sk-guard-..." python your_app.py
With fitguard run still up in its own terminal from Step 2, open http://localhost:8787/dashboard and confirm your test request shows up there. That's your proof the wiring is correct before anything touches production.
Step 5 — Deploy to production
Your app's deployment doesn't change — it still just needs FITGUARD_URL and
FITGUARD_KEY set to wherever FitGuard is reachable in production, exactly like any other
environment variable your app already uses for a service URL. FitGuard itself needs to be running somewhere your app
can reach it; see Deployment for the actual platform steps (Docker, Render, Railway,
Heroku, a VPS). It is a separate deploy from your application, the same way a database is.