Providers

OpenAI, Anthropic, Gemini, Groq, and Together all go through the same endpoint, the same virtual key, and the same client code. The only thing that changes between them is the model field.

How this works

FitGuard exposes one endpoint, shaped like OpenAI's chat completions API: POST /v1/chat/completions. It reads the model field, works out which upstream provider that model belongs to, translates the request into that provider's native format if needed, and translates the response back. Your client only ever speaks the OpenAI shape. It never sees Anthropic's or Gemini's actual request format, and it never sees your real provider keys.

Every example on this page uses the same two values: your FitGuard URL as base_url, and a virtual key from fitguard init as the API key. Switching providers means changing one string, the model name. Nothing else in your code changes.

OpenAI

Model names pass through unchanged: gpt-4o, gpt-4o-mini, o1, and so on. Vision and tool calling work exactly as documented in OpenAI's own API reference, since no translation is needed.

curl https://your-fitguard-host/v1/chat/completions \
  -H "Authorization: Bearer sk-guard-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "hello"}]
  }'
from openai import OpenAI

client = OpenAI(base_url="https://your-fitguard-host/v1",
    api_key="sk-guard-...")
resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "hello"}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://your-fitguard-host/v1",
  apiKey: "sk-guard-...",
});

const resp = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "hello" }],
});
console.log(resp.choices[0].message.content);
using System.Net.Http.Json;

// Initialize the HTTP client with the AI guard host URL and API key.
var client = new HttpClient
{
    BaseAddress = new Uri("https://your-fitguard-host/v1/"),
};

// Set the Authorization header with the AI guard API key.
client.DefaultRequestHeaders.Authorization =
    new System.Net.Http.Headers.AuthenticationHeaderValue
    ("Bearer", "sk-guard-...");

// Define the request payload.
var payload = new {
    model = "gpt-4o",
    messages = new[] 
    { 
        new { role = "user", content = "hello" },
        new { role = "system", content = "You are an assistant." } 
    }
};

// Send the POST request to the AI guard host.
var response = await client.PostAsJsonAsync
    ("chat/completions", payload);

// Read the response body as a string.
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.util.concurrent.CompletableFuture;

// Create the HTTP client.
HttpClient client = HttpClient.newHttpClient();

// Create the request body string.
String json = """
    {
        "model": "gpt-4o",
        "messages": [
            { "role": "user", "content": "hello" },
            { "role": "system", "content": "You are an assistant." }
        ]
    }
    """;

// Create the request body publisher.
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://your-fitguard-host/v1/chat/completions"))
    .header("Authorization", "Bearer sk-guard-...")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

// Send the request and get the response.
HttpResponse<String> response = client.send
    (request, HttpResponse.BodyHandlers.ofString());

// Read the response body as a string.
System.out.println(response.body());
body, _ := json.Marshal(map[string]any{
    "model": "gpt-4o",
    "messages": []map[string]string{
        {"role": "user", "content": "hello"},
        {"role": "system", "content": "You are an assistant."},
    },
})
req, _ := http.NewRequest("POST", "https://your-fitguard-host/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer sk-guard-...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
let client = reqwest::blocking::Client::new();
let resp = client.post("https://your-fitguard-host/v1/chat/completions")
    .bearer_auth("sk-guard-...")
    .json(&serde_json::json!({
        "model": "gpt-4o",
        "messages": [
            {"role": "user", "content": "hello"},
            {"role": "system", "content": "You are an assistant."}
        ]
    }))
    .send()?;

Anthropic

Use Claude model names: claude-sonnet-5, claude-haiku-4-5, and so on. FitGuard translates the request into Anthropic's Messages API and translates the response back into the OpenAI shape your client already expects, so the client code below is identical to the OpenAI examples except for the model name.

If your app currently calls Claude with Anthropic's own SDK (anthropic.Anthropic(...), .messages.create(...)), see Already using Anthropic's own SDK? in the getting started guide. It's not a drop-in swap the way switching OpenAI models is: Anthropic's client speaks a different request shape than FitGuard's OpenAI-compatible endpoint expects, so that section walks through the actual change needed.
curl https://your-fitguard-host/v1/chat/completions \
  -H "Authorization: Bearer sk-guard-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "messages": [{"role": "user", "content": "hello"}]
  }'
from openai import OpenAI

client = OpenAI(base_url="https://your-fitguard-host/v1", 
    api_key="sk-guard-...")
    
resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "hello"}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://your-fitguard-host/v1",
  apiKey: "sk-guard-...",
});

const resp = await client.chat.completions.create({
  model: "claude-sonnet-5",
  messages: [{ role: "user", content: "hello" }],
});
console.log(resp.choices[0].message.content);
using System.Net.Http.Json;

var client = new HttpClient
{
    BaseAddress = new Uri("https://your-fitguard-host/v1/"),
};

// Set the authorization header.
client.DefaultRequestHeaders.Authorization =
    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",
        "sk-guard-...");

var payload = new {
    model = "claude-sonnet-5",
    messages = new[] { new { role = "user", content = "hello" } }
};

var response = await client.PostAsJsonAsync("chat/completions",
    payload);
    
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
HttpClient client = HttpClient.newHttpClient();

String json = """
    {
        "model":"claude-sonnet-5",
        "messages":[
            {"role":"user","content":"hello"},
            {"role":"system","content":"You are an assistant."},
        ]
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://your-fitguard-host/v1/chat/completions"))
    .header("Authorization", "Bearer sk-guard-...")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());
    
System.out.println(response.body());
body, _ := json.Marshal(map[string]any{
    "model": "claude-sonnet-5",
    "messages": []map[string]string{
        {"role": "user", "content": "hello"},
        {"role": "system", "content": "You are an assistant."},
    },
})
req, _ := http.NewRequest("POST", "https://your-fitguard-host/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer sk-guard-...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
let client = reqwest::blocking::Client::new();
let resp = client.post("https://your-fitguard-host/v1/chat/completions")
    .bearer_auth("sk-guard-...")
    .json(&serde_json::json!({
        "model": "claude-sonnet-5",
        "messages": [
            {"role": "user", "content": "hello"},
            {"role": "system", "content": "You are an assistant."}
        ]
    }))
    .send()?;

Gemini

Use Gemini model names: gemini-2.5-pro, gemini-2.5-flash, gemini-1.5-pro, and so on. FitGuard translates the request into Gemini's generateContent format and translates the response back, the same way it does for Anthropic.

curl https://your-fitguard-host/v1/chat/completions \
  -H "Authorization: Bearer sk-guard-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-2.5-flash",
    "messages": [{"role": "user", "content": "hello"}]
  }'
from openai import OpenAI

client = OpenAI(base_url="https://your-fitguard-host/v1",
    api_key="sk-guard-...")
    
resp = client.chat.completions.create(
    model="gemini-2.5-flash",
    messages=[{"role": "user", "content": "hello"}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://your-fitguard-host/v1",
  apiKey: "sk-guard-...",
});

const resp = await client.chat.completions.create({
  model: "gemini-2.5-flash",
  messages: [{ role: "user", content: "hello" }],
});
console.log(resp.choices[0].message.content);
using System.Net.Http.Json;

var client = new HttpClient {
    BaseAddress = new Uri("https://your-fitguard-host/v1/"),
};
client.DefaultRequestHeaders.Authorization =
    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",
        "sk-guard-...");

var payload = new {
    model = "gemini-2.5-flash",
    messages = new[] { new { role = "user", content = "hello" } }
};

var response = await client.PostAsJsonAsync("chat/completions",
    payload);

var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
HttpClient client = HttpClient.newHttpClient();

String json = """
    {
        "model": "gemini-2.5-flash",
        "messages": [
            {
                "role": "user",
                "content": "hello"
            }
        ]
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://your-fitguard-host/v1/chat/completions"))
    .header("Authorization", "Bearer sk-guard-...")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(request, 
    HttpResponse.BodyHandlers.ofString());

System.out.println(response.body());
body, _ := json.Marshal(map[string]any{
    "model":    "gemini-2.5-flash",
    "messages": []map[string]string{{"role": "user", "content": "hello"}},
})
req, _ := http.NewRequest("POST", "https://your-fitguard-host/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer sk-guard-...")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
let client = reqwest::blocking::Client::new();
let resp = client.post("https://your-fitguard-host/v1/chat/completions")
    .bearer_auth("sk-guard-...")
    .json(&serde_json::json!({
        "model": "gemini-2.5-flash",
        "messages": [{"role": "user", "content": "hello"}]
    }))
    .send()?;

One real limitation, not a bug: if you send an image as a remote URL (image_url.url pointing at https://...), Gemini rejects it. Gemini only accepts inline base64 image data or a file already uploaded through its own File API, and FitGuard does not fetch remote URLs on your behalf to convert them, since that would let a request make FitGuard's server fetch arbitrary attacker-supplied URLs. Send images as base64 data URIs (data:image/png;base64,...) when using Gemini. OpenAI and Anthropic both accept remote image URLs directly, so this restriction is specific to Gemini.

Groq

Use Groq's hosted model names: llama-3.3-70b-versatile, llama-3.1-8b-instant, mixtral-8x7b-32768, and so on. Groq's API is already OpenAI-compatible, so requests pass through with no translation.

curl https://your-fitguard-host/v1/chat/completions \
  -H "Authorization: Bearer sk-guard-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama-3.3-70b-versatile",
    "messages": [{"role": "user", "content": "hello"}]
  }'

Together AI

Use Together's model names, which are namespaced by publisher, e.g. meta-llama/Llama-3-70b-chat-hf. Together's API is also OpenAI-compatible. A model name containing a / that isn't a recognized provider prefix (openai/, anthropic/, etc.) routes to Together by default, since that's the convention its own model names use.

curl https://your-fitguard-host/v1/chat/completions \
  -H "Authorization: Bearer sk-guard-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-3-70b-chat-hf",
    "messages": [{"role": "user", "content": "hello"}]
  }'

Mixing providers in one app

Nothing about the client changes between providers, so a single app can call all five without any conditional logic. The only difference is which string you put in model:

await client.chat.completions.create({ model: "gpt-4o-mini", ... });                     // OpenAI
await client.chat.completions.create({ model: "claude-haiku-4-5", ... });                  // Anthropic
await client.chat.completions.create({ model: "gemini-2.5-flash", ... });                // Gemini
await client.chat.completions.create({ model: "llama-3.3-70b-versatile", ... });         // Groq
await client.chat.completions.create({ model: "meta-llama/Llama-3-70b-chat-hf", ... });  // Together

Each of these still goes through the same cache, the same budget check, and the same cost log. Adding a provider you don't already have configured is one command: fitguard add-provider. See Fallback in the configuration reference if you want FitGuard to try a different provider automatically when the primary one fails.

Esc