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.

This is the step people most often get wrong by skipping it: the After column uses the OpenAI client/package, not Anthropic's, even though the model is still Claude. That's not a typo in this guide — FitGuard's endpoint speaks the OpenAI request format regardless of which provider serves the model.

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.

The two things people mix up here: (1) FitGuard installs on your machine/server, never inside a Python virtualenv (or npm/NuGet/Maven/Cargo project), and (2) your application needs the OpenAI client library for your language installed and declared as a dependency — FitGuard being installed does not put that library anywhere. They're two separate installs for two separate things.
Esc