# LLM Gateway — Full Documentation
> LLM Gateway is an open-source, OpenAI-compatible API gateway for routing, managing, and analyzing requests across LLM providers. Use one API key, track usage and cost, configure caching and guardrails, and self-host or use the managed cloud. Current models and pricing: https://llmgateway.io/models
API base URL: https://api.llmgateway.io/v1 · Docs: https://docs.llmgateway.io · Site: https://llmgateway.io
This file concatenates the full text of every documentation page below.
## Contents
Start with https://docs.llmgateway.io/llms.txt for the full page index. Read a linked Markdown page when you need one topic; this complete file may exceed an agent context window.
- [Airside](https://docs.llmgateway.io/llms.mdx/airside)
- [DevPass](https://docs.llmgateway.io/llms.mdx/devpass)
- [Lounge](https://docs.llmgateway.io/llms.mdx/lounge)
- [Introduction to LLM Gateway](https://docs.llmgateway.io/llms.mdx/index)
- [Overview](https://docs.llmgateway.io/llms.mdx/overview)
- [Quickstart](https://docs.llmgateway.io/llms.mdx/quick-start)
- [Coding tool guides](https://docs.llmgateway.io/llms.mdx/guides)
- [Health check](https://docs.llmgateway.io/llms.mdx/health)
- [Create speech](https://docs.llmgateway.io/llms.mdx/v1_audio_speech)
- [Create transcription](https://docs.llmgateway.io/llms.mdx/v1_audio_transcriptions)
- [Chat Completions](https://docs.llmgateway.io/llms.mdx/v1_chat_completions)
- [Decisions](https://docs.llmgateway.io/llms.mdx/v1_decisions)
- [Embeddings](https://docs.llmgateway.io/llms.mdx/v1_embeddings)
- [Edit image](https://docs.llmgateway.io/llms.mdx/v1_images_edits)
- [Create image](https://docs.llmgateway.io/llms.mdx/v1_images_generations)
- [Retrieve key status](https://docs.llmgateway.io/llms.mdx/v1_key_retrieve)
- [Anthropic Messages](https://docs.llmgateway.io/llms.mdx/v1_messages)
- [Models](https://docs.llmgateway.io/llms.mdx/v1_models)
- [Moderations](https://docs.llmgateway.io/llms.mdx/v1_moderations)
- [OCR](https://docs.llmgateway.io/llms.mdx/v1_ocr)
- [Create a realtime client secret](https://docs.llmgateway.io/llms.mdx/v1_realtime_client_secrets)
- [Rerank](https://docs.llmgateway.io/llms.mdx/v1_rerank)
- [Search](https://docs.llmgateway.io/llms.mdx/v1_search)
- [System One](https://docs.llmgateway.io/llms.mdx/v1_systemone)
- [Video content](https://docs.llmgateway.io/llms.mdx/v1_videos_content)
- [Create video](https://docs.llmgateway.io/llms.mdx/v1_videos_create)
- [Video log content](https://docs.llmgateway.io/llms.mdx/v1_videos_log_content)
- [Retrieve video](https://docs.llmgateway.io/llms.mdx/v1_videos_retrieve)
- [LLM Gateway Developer Resources](https://docs.llmgateway.io/llms.mdx/developers)
- [Introduction](https://docs.llmgateway.io/llms.mdx/learn)
- [Self Host LLM Gateway](https://docs.llmgateway.io/llms.mdx/self-host)
# Airside
URL: https://docs.llmgateway.io/airside
Airside is where the companies behind the models run their listing on LLM Gateway. It uses an airport metaphor: LLM Gateway is the airport, developers are passengers, and providers are **carriers**. As a carrier you claim your provider, register your fleet of models, file your fares, and see the traffic that reaches you.
## Quick start [#quick-start]
### Sign up with your company email [#sign-up-with-your-company-email]
Create an account at [airside.llmgateway.io](https://airside.llmgateway.io). The domain of your verified email must match your provider's API endpoint or website to claim it.
### Claim or register your carrier [#claim-or-register-your-carrier]
Claim an existing catalogue provider, or register a new one. The LLM Gateway team reviews every claim before the carrier goes live.
### Register your fleet and file fares [#register-your-fleet-and-file-fares]
Add each model with its context size and capabilities, and file its per-token prices. Every model must pass preflight, which runs on your **testing key**. A newly registered carrier also files its **provider key** with its first model: the separate key we serve your traffic with, so test traffic bills apart from live traffic. It is smoke-tested against that model when you file and goes live once the model is approved; replacing it later under **Settings** is smoke-tested the same way and reviewed by the LLM Gateway team. Once a filing is approved, the model is routable as `/`.
### Watch your traffic [#watch-your-traffic]
The **Traffic** page shows requests, errors, tokens, and billed traffic per model. Invite teammates under **Crew** so your whole team can manage the listing.
## Take the tour [#take-the-tour]
## Explore Airside [#explore-airside]
## Other products [#other-products]
# DevPass
URL: https://docs.llmgateway.io/devpass
DevPass is a monthly subscription for developers who code with AI agents. Instead of topping up credits per request, you pay one flat price and get a monthly allowance that covers the models your tools call. Your DevPass key works anywhere an OpenAI- or Anthropic-compatible key does, so you keep the tools you already use.
## Quick start [#quick-start]
### Pick a plan [#pick-a-plan]
Choose a plan on the [DevPass pricing page](https://devpass.llmgateway.io/pricing). Plans come in three tiers, **Lite**, **Pro**, and **Max**. Higher tiers raise the monthly allowance and the weekly cap on [premium models](https://docs.llmgateway.io/learn/model-categories).
### Copy your key [#copy-your-key]
Open the [DevPass dashboard](https://devpass.llmgateway.io/dashboard). Rotate the key under **API Key** to reveal it once. The **Quick start** panel next to it then shows the exact setup for each supported tool.
### Point your tool at DevPass [#point-your-tool-at-devpass]
For Claude Code, set two environment variables and run `claude` as usual:
```bash
export ANTHROPIC_BASE_URL=https://api.llmgateway.io
export ANTHROPIC_AUTH_TOKEN=your_devpass_key
claude
```
OpenAI-compatible tools use `OPENAI_BASE_URL=https://api.llmgateway.io/v1` and `OPENAI_API_KEY` instead. Every other tool has its own [setup guide](https://docs.llmgateway.io/guides).
### Watch your usage [#watch-your-usage]
Requests appear in [Coding Agents](https://docs.llmgateway.io/learn/coding-agents), broken down by tool. Allowance meters on the dashboard show what is left this month and this week.
## Automatic cache duration [#automatic-cache-duration]
Under **Settings → Automatic cache duration**, choose **5 minutes** (default) or **1 hour**. Available in Automatic cache-write mode, this controls automatically added markers only when your request contains no cache markers of its own. One hour applies only to supported models and providers; otherwise the default duration is used. Longer durations can increase cache-write costs.
Your tool's own markers take precedence, including markers without a TTL. Client-managed and Disabled modes ignore this preference. See [Provider Cache Control](https://docs.llmgateway.io/features/caching/provider-cache-control) for details.
## Cancel your subscription [#cancel-your-subscription]
Cancel from **Billing** in the DevPass dashboard. A paid plan stays active through its current billing period. If a renewal is unpaid or still pending, cancellation ends the plan immediately and attempts to void its pending invoices. Immediate cancellation stops automatic collection, even if invoice cleanup needs to be retried.
If Stripe's invoice lookup is unavailable, cancellation also ends the plan immediately to prevent further renewal attempts. The confirmation tells you whether the plan ended now or remains active until the period ends.
## Take the tour [#take-the-tour]
## Explore DevPass [#explore-devpass]
## Other products [#other-products]
# Lounge
URL: https://docs.llmgateway.io/lounge
Lounge is the chat app built on LLM Gateway. Pick any model from the catalogue, switch mid-conversation, and compare answers side by side. The same app includes studios for images, video, and speech, plus live voice calls, on the web at [lounge.llmgateway.io](https://lounge.llmgateway.io) and on iPhone and iPad.
## Quick start [#quick-start]
### Sign in [#sign-in]
Open [lounge.llmgateway.io](https://lounge.llmgateway.io) and sign in with your LLM Gateway account.
### Choose a model [#choose-a-model]
Use the model picker above the composer. **Auto Route** picks a provider for you based on availability and cost.
### Chat, compare, or create [#chat-compare-or-create]
Send a message, turn on **Comparison mode** to ask several models at once, or pick **Group**, **Image**, **Video**, **Audio**, or **Voice** in the sidebar.
### Get more with a membership [#get-more-with-a-membership]
A [Lounge membership](https://docs.llmgateway.io/learn/chat-plans) gives you monthly credits worth more than you pay, so heavy chat usage costs less than pay-as-you-go.
## Take the tour [#take-the-tour]
## Explore Lounge [#explore-lounge]
## Other products [#other-products]
# Introduction to LLM Gateway
URL: https://docs.llmgateway.io/
LLM Gateway is an open-source API gateway that sits between your applications and LLM providers like OpenAI, Anthropic, Google AI Studio, and more. It provides a unified, OpenAI-compatible API interface with built-in cost tracking, caching, and intelligent routing.
## Products [#products]
These docs cover four products. Pick one from the menu at the top of the sidebar, or start here:
## How it works [#how-it-works]
Point your existing SDK at `https://api.llmgateway.io/v1` (or your self-hosted instance) and authenticate with an LLM Gateway API key — no code rewrites. The gateway routes each request to the right provider, tracks tokens and cost per model, provider, project, and API key, and fails over to a healthy provider when one errors. Pay per-token with prepaid credits at provider list rates, or bring your own provider keys (BYOK) for free.
## Why use a gateway? [#why-use-a-gateway]
* **One integration** — switch models or providers by changing a model string, not your code.
* **Cost visibility** — usage analytics and cost breakdowns across every provider in one dashboard.
* **Reliability** — automatic provider failover when a provider fails, plus opt-in response caching per project.
* **No lock-in** — open source (AGPLv3), self-hostable, and OpenAI-compatible end to end.
## Take the tour [#take-the-tour]
## Features [#features]
All features are documented under https://docs.llmgateway.io/features; each feature page is included in full in this file.
## AI Tooling [#ai-tooling]
LLM Gateway is built to work seamlessly with AI agents and development tools.
AI tooling: https://docs.llmgateway.io/llms.txt (docs index for LLMs), https://docs.llmgateway.io/llms-full.txt (this file), https://docs.llmgateway.io/developers/mcp (MCP server), https://docs.llmgateway.io/guides/agent-skills (agent skills), and https://llmgateway.io/templates (templates and agents).
## Next Steps [#next-steps]
* [**Quickstart**](https://docs.llmgateway.io/quick-start) — Get up and running in minutes
* [**Overview**](https://docs.llmgateway.io/overview) — Learn more about what LLM Gateway offers
* [**Self-Host**](https://docs.llmgateway.io/self-host) — Deploy on your own infrastructure
# Overview
URL: https://docs.llmgateway.io/overview
LLM Gateway is an open-source API gateway for Large Language Models (LLMs). It acts as a middleware between your applications and various LLM providers, allowing you to:
* Route requests to multiple LLM providers (OpenAI, Anthropic, Google AI Studio, and others)
* Manage API keys for different providers in one place
* Track token usage and costs across all your LLM interactions
* Analyze performance metrics to optimize your LLM usage
## Analyzing Your LLM Requests [#analyzing-your-llm-requests]
LLM Gateway provides detailed insights into your LLM usage:
* **Usage Metrics**: Track the number of requests, tokens used, and response times
* **Cost Analysis**: Monitor spending across different models and providers
* **Performance Tracking**: Identify patterns and optimize your prompts based on actual usage data
* **Breakdown by Model**: Compare different models' performance and cost-effectiveness
All this data is automatically collected and presented in an intuitive dashboard, helping you make informed decisions about your LLM strategy.
## Getting Started [#getting-started]
Using LLM Gateway is simple. Just swap out your current LLM provider URL with the LLM Gateway API endpoint:
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
]
}'
```
LLM Gateway maintains compatibility with the OpenAI API format, making migration seamless. Note that unknown or unsupported request parameters (for example `stop`, `seed`, `logprobs`, or `logit_bias`) are accepted and silently ignored rather than rejected, so a request carrying them still succeeds — the parameter just has no effect.
## Hosted vs. Self-Hosted [#hosted-vs-self-hosted]
You can use LLM Gateway in two ways:
* **Hosted Version**: For immediate use without setup, visit [llmgateway.io](https://llmgateway.io) to create an account and get an API key.
* **Self-Hosted**: Deploy LLM Gateway on your own infrastructure for complete control over your data and configuration.
The self-hosted version offers additional customization options and ensures your LLM traffic never leaves your infrastructure if desired.
# Quickstart
URL: https://docs.llmgateway.io/quick-start
Welcome to **LLM Gateway**—a single drop‑in endpoint that lets you call today’s best large‑language models while keeping **your existing code** and development workflow intact.
> **TL;DR** — Point your HTTP requests to `https://api.llmgateway.io/v1/…`, supply your `LLM_GATEWAY_API_KEY`, and you’re done.
***
## 1 · Get an API key [#1--get-an-api-key]
1. Sign in to the dashboard.
2. Create a new Project → *Copy the key*.
3. Export it in your shell (or a `.env` file):
```bash
export LLM_GATEWAY_API_KEY="llmgtwy_XXXXXXXXXXXXXXXX"
```
***
## 2 · Pick your language [#2--pick-your-language]
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
]
}'
```
```typescript
const response = await fetch("https://api.llmgateway.io/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.LLM_GATEWAY_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello, how are you?" }],
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data.choices[0].message.content);
```
```tsx
import { useState } from "react";
function ChatComponent() {
const [response, setResponse] = useState("");
const [loading, setLoading] = useState(false);
const sendMessage = async () => {
setLoading(true);
try {
const res = await fetch("https://api.llmgateway.io/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.REACT_APP_LLM_GATEWAY_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello, how are you?" }],
}),
});
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const data = await res.json();
setResponse(data.choices[0].message.content);
} catch (error) {
console.error("Error:", error);
} finally {
setLoading(false);
}
};
return (
{response &&
{response}
}
);
}
export default ChatComponent;
```
```typescript
// app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const { message } = await request.json();
const response = await fetch(
"https://api.llmgateway.io/v1/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.LLM_GATEWAY_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: message }],
}),
},
);
if (!response.ok) {
return NextResponse.json(
{ error: "Failed to get response" },
{ status: response.status },
);
}
const data = await response.json();
return NextResponse.json({
message: data.choices[0].message.content,
});
}
// Usage in component:
// const response = await fetch('/api/chat', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ message: 'Hello, how are you?' })
// });
```
```python
import requests
import os
response = requests.post(
'https://api.llmgateway.io/v1/chat/completions',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {os.getenv("LLM_GATEWAY_API_KEY")}'
},
json={
'model': 'gpt-4o',
'messages': [
{'role': 'user', 'content': 'Hello, how are you?'}
]
}
)
response.raise_for_status()
print(response.json()['choices'][0]['message']['content'])
```
```java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
String apiKey = System.getenv("LLM_GATEWAY_API_KEY");
String requestBody = """
{
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
]
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.llmgateway.io/v1/chat/completions"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey)
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```rust
use reqwest::Client;
use serde_json::json;
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box> {
let client = Client::new();
let api_key = env::var("LLM_GATEWAY_API_KEY")?;
let response = client
.post("https://api.llmgateway.io/v1/chat/completions")
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.json(&json!({
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
]
}))
.send()
.await?;
let result: serde_json::Value = response.json().await?;
println!("{}", result["choices"][0]["message"]["content"]);
Ok(())
}
```
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type ChatRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
func main() {
apiKey := os.Getenv("LLM_GATEWAY_API_KEY")
requestBody := ChatRequest{
Model: "gpt-4o",
Messages: []Message{{Role: "user", Content: "Hello, how are you?"}},
}
jsonData, _ := json.Marshal(requestBody)
req, _ := http.NewRequest("POST", "https://api.llmgateway.io/v1/chat/completions", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
fmt.Println("Response received")
}
```
```php
'gpt-4o',
'messages' => [
['role' => 'user', 'content' => 'Hello, how are you?']
]
];
$options = [
'http' => [
'header' => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
],
'method' => 'POST',
'content' => json_encode($data)
]
];
$context = stream_context_create($options);
$response = file_get_contents(
'https://api.llmgateway.io/v1/chat/completions',
false,
$context
);
if ($response === FALSE) {
throw new Exception('Request failed');
}
$result = json_decode($response, true);
echo $result['choices'][0]['message']['content'];
?>
```
```ruby
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.llmgateway.io/v1/chat/completions')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{ENV['LLM_GATEWAY_API_KEY']}"
request.body = {
model: 'gpt-4o',
messages: [
{ role: 'user', content: 'Hello, how are you?' }
]
}.to_json
response = http.request(request)
if response.code != '200'
raise "HTTP Error: #{response.code}"
end
result = JSON.parse(response.body)
puts result['choices'][0]['message']['content']
```
***
## 3 · SDK integrations [#3--sdk-integrations]
```ts title="ai-sdk.ts"
import { llmgateway } from "@llmgateway/ai-sdk-provider";
import { generateText } from "ai";
const { text } = await generateText({
model: llmgateway("gpt-4o"),
prompt: "Write a vegetarian lasagna recipe for 4 people.",
});
```
```ts title="vercel-ai-sdk.ts"
import { createOpenAI } from "@ai-sdk/openai";
const llmgateway = createOpenAI({
baseURL: "https://api.llmgateway.io/v1",
apiKey: process.env.LLM_GATEWAY_API_KEY!,
});
const completion = await llmgateway.chat({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello, how are you?" }],
});
console.log(completion.choices[0].message.content);
```
```ts title="openai-sdk.ts"
import OpenAI from "openai";
const openai = new OpenAI({
baseURL: "https://api.llmgateway.io/v1",
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello, how are you?" }],
});
console.log(completion.choices[0].message.content);
```
***
## 4 · Going further [#4--going-further]
* **Streaming**: pass `stream: true` to any request—the gateway normalizes every provider's stream into OpenAI-format SSE chunks, with a final chunk carrying `usage` and routing metadata before `data: [DONE]`.
* **Monitoring**: Every call appears in the dashboard with latency, cost & provider breakdown.
***
## 5 · FAQ [#5--faq]
See the [Models page](https://llmgateway.io/models).
Unlike OpenRouter, we offer:
Full self-hosting capabilities, giving you complete control over your
infrastructure
Enhanced analytics with deeper insights into your model usage and
performance
No fees when using your own provider keys, maximizing cost efficiency
Greater flexibility and customization options for enterprise deployments
Our pricing structure is designed to be flexible and cost-effective: See the
[Pricing section](https://llmgateway.io#pricing).
***
## 6 · Next steps [#6--next-steps]
* Read [Self host docs](https://docs.llmgateway.io/self-host) guide.
* Drop into our [GitHub](https://github.com/theopenco/llmgateway) for help or feature requests.
Happy building! ✨
# Airside for Providers
URL: https://docs.llmgateway.io/features/airside
[Airside](https://airside.llmgateway.io) is the self-serve console for the companies behind the models — the [providers](https://llmgateway.io/providers) LLM Gateway routes to. It runs on an airport metaphor: LLM Gateway is the airport, developers are passengers, and providers are **carriers**. As a carrier you claim your provider, register your fleet of models, file your fares, and watch traffic arrive.
## Claiming your carrier [#claiming-your-carrier]
Sign up at [airside.llmgateway.io](https://airside.llmgateway.io) with your **company email** and verify it. A catalogue provider is claimable when the registrable domain of your verified email matches the registrable domain of the provider's API endpoint (or its website) — if your API is served from `api.example.ai`, an `@example.ai` address can claim it. Subdomains collapse to the registrable domain, so `ops@mail.example.ai` works too.
If your email is on a different domain than your API, prove the API's domain over DNS instead. On the onboarding page, add each domain under your company and publish the shown TXT record on `_llmgateway-airside.`. Every domain verified this way counts like your email domain for claiming, registering a carrier, and crew invites. The onboarding page lists each domain with how it was proven: over DNS, or by a verified email when a claim was matched on your email domain. An email proof belongs to the person who filed the claim, so only DNS-verified domains carry over to the rest of your crew.
Every claim is reviewed by the LLM Gateway team before the carrier goes live; a rejected claim shows the review note so you can follow up. One company can operate several carriers — regional deployments or separate brands all live under one console.
Listing on llmgateway.io carries a one-time **$2,500 listing fee** per provider company, paid during onboarding. Providers we already work with receive an **invite code** that waives it — entered in the same onboarding step instead of paying. See the [pricing summary](https://airside.llmgateway.io/pricing.md) for the full economics.
## Crew [#crew]
A listing covers your whole team. Under **Crew**, company owners invite teammates by email — up to **10 members** per company, pending invites included. Invites are limited to your team's domains (the ones that prove your claims); a teammate with an existing account joins instantly, anyone else joins the first time they sign in with the invited address.
## Registering a new carrier [#registering-a-new-carrier]
Not in the [provider catalogue](https://llmgateway.io/providers) yet? Register your provider as a **new carrier** from the onboarding page: pick a carrier id, display name, and the base URL of your **OpenAI-compatible API** (the gateway calls `/v1/chat/completions`, unless a listing picks a different [upstream API](#upstream-api-and-preflight)). Enter the base URL only: a URL whose path contains `/v1` or `/chat/completions` is rejected, because the gateway appends the endpoint path itself and the doubled path would fail every request. The endpoint must live on your verified email's domain or a domain your company verified over DNS — the same anti-squatting rule as claiming — and registrations go through the same review. Once approved, your carrier appears on the public providers and models pages and its listings route like any other provider's.
## Listing your fleet [#listing-your-fleet]
Once your claim is active, register models under **Fleet**: model name, display name, description, family, context size, max output, capability flags (streaming, vision, audio input, tools, JSON output, reasoning), the supported `reasoning_effort` tiers, the `tool_choice` modes your endpoint accepts, and optionally the weight **quantization** you serve (`int4`, `int8`, `fp4`, `fp6`, `fp8`, `fp16`, `bf16`, or `fp32`). Approved quantization shows on the Fleet card and the public model card. If your API expects a different id than the one developers use, set the **upstream model ID**; like the upstream API, it is fixed once the model is listed.
A listing can also carry optional **rate limits** in requests per minute and per day. Choose whether the cap is shared **across all organizations** (the total traffic sent to your deployment stays under it) or applies **per organization** (each organization gets its own counter, so total load grows with the number of customers). Platform-set limits always take precedence. A new listing is created as a draft together with an **initial price filing**; when the LLM Gateway team approves that filing, the model goes active.
Registering a model that already exists in the catalogue reuses its identity: display name, description, and family come from the catalogue model, and **Use catalog price** copies another provider's flat prices into your initial tariff as a starting point. For a new model, the family field suggests existing catalogue families.
Draft listings save immediately. On a live listing, a metadata change (description, context size, capabilities, quantization, and so on) is filed for review and applies once approved. Saving again while that change is pending replaces it in place, and saving the live values withdraws it. A pending price filing blocks metadata edits until it is reviewed.
Carriers claiming a catalogue provider can also **import their catalogue models** in one click: each active catalogue model becomes a managed listing with its current price as an approved filing. Once approved, the listing serves the model at its filed fares and keeps the catalogue entry's request handling. Pausing or delisting an imported model takes it out of service instead of falling back to the catalogue entry; relisting or resuming puts it back. The LLM Gateway team can then remove the provider's static definition and mappings; only its active Airside listings remain available.
### Catalogue removal [#catalogue-removal]
While the static provider definition exists, routing and BYOK key validation use
its endpoint configuration. An Airside base URL configured in advance takes effect
after that definition is removed; a BYOK key's explicit base URL still overrides it.
When a provider's static catalogue definition is removed, its existing active
Airside claim serves from its configured base URL, managed credentials, and
active listings. Models without an active Airside listing are no longer offered by that provider.
The carrier's declared profile then drives compliance routing. Later profile
edits apply directly; missing facts do not satisfy compliance requirements.
Provider IDs, BYOK keys, approved prices, and historical usage retain their
existing identity. Configure the carrier endpoint before adding managed serving
credentials. Revoking the claim stops its listings from serving while preserving
the catalogue provider record and platform-owned credentials.
### Upstream API and preflight [#upstream-api-and-preflight]
Each listing also picks the **upstream API** the gateway speaks to it: the carrier default, OpenAI Chat Completions, OpenAI Responses, or Google Vertex `generateContent`. The choice is independent of the capabilities you declare — tool calls, JSON output, vision, reasoning, and web search are all probed and served through whichever API you picked, so pick the one your endpoint actually implements. It is fixed once the model is listed.
Before a listing can be filed, Airside runs a **preflight** against your endpoint through that API: one check per declared capability, plus one for each declared limit. The **context size** check sends a prompt filling about 70% of the declared window, up to 2 million tokens, and fails if your endpoint refuses it or reports far fewer input tokens than were sent; the **max output** check requests the full declared output budget and fails if your endpoint refuses it. A refusal is reported with the probe that was sent followed by your endpoint's own answer. A request that times out is retried up to twice; a check that passes on a retry still passes, with a warning that your endpoint may be slow or overloaded. Some checks are **optional** for now: a response that misses one still passes its check, with the miss shown as a warning marked **Optional**. Optional checks may become required later. These are currently optional:
* **Basic completion** requires `usage` with positive input and output token counts, cached tokens counted within the input, and, on Chat Completions, a response `id`.
* **Streaming** requires a usage report (on Chat Completions, honour `stream_options.include_usage` with a final usage chunk). The streamed input token count must match the non-streaming request for the same prompt; usage sent as per-chunk increments is flagged, because the last reported value is what gets billed.
* **Reasoning** requires evidence that the model reasoned: reasoning text, a reasoning block or reasoning tokens in `usage`. An endpoint that accepts `reasoning_effort` but ignores it is flagged.
A stream must already contain assistant text and a finish reason, or the **streaming** check fails. The **tool calls** check is required too: on Chat Completions, a response with `tool_calls` must finish with `finish_reason: "tool_calls"` (a named `tool_choice` may finish with `stop`), or the check fails, because clients stop instead of running the tool. Since no other `tool_choice` mode fixes this, it never narrows your modes.
The context check is billed like any other large prompt. You supply a provider API key that can call the model, saved as this carrier's **test key** under Settings so later runs — yours and ours — reuse it. Use a key separate from the one behind your live integration: preflight traffic is billed by your own platform and is not tracked in LLMGateway usage or billing. You can replace or remove it at any time. Editing the model, its limits included, invalidates the run, so a listing never ships with checks made against different settings.
Re-verifying a live listing reports, it does not rewrite: a failed check names the capability your endpoint refused and leaves the listing exactly as you declared it, because one refused request is as easily a transient fault as a missing capability. Fix the endpoint and verify again, or switch the capability off yourself if the deployment genuinely does not do it. Fleet marks the listing **In service · unverified** until a run passes again.
A capability you have edited on a live listing lives in a filing until we approve it, and preflight verifies **that** shape rather than the one currently serving — otherwise a run would pass without ever exercising the capability you are trying to prove. A failed check is reported next to the filing rather than removed from it; the reviewer decides.
Each check lists the upstream requests it actually sent. A check can be more than one request: the tool check walks the `tool_choice` modes and the reasoning check sweeps the effort tiers, so every attempt appears nested under its check with its own pass or fail and the upstream message on failure. The breakdown fills in while the run is still going. A check fails only when every one of its requests failed.
Where a check can tell the difference between a capability and a dialect, it narrows the listing itself. A `tool_choice` mode your endpoint mishandles is dropped while tool calls stay on, and a `reasoning_effort` tier your endpoint rejects outright is retried at another tier — reasoning stays on and only the rejected tiers come off your declared set. If one tier is rejected the check goes on to try the others, since a tier left declared is one the gateway will forward verbatim. So a deployment implementing only a subset of the tiers verifies without losing reasoning, and you do not have to know which subset in advance.
## Price filings [#price-filings]
Listed prices are what developers are billed, so pricing never changes silently: it only changes through a reviewed **price filing**.
* The **initial** filing sets the launch pricing and activates the model on approval.
* Every later price change is an **update** filing — drafted by you, approved by the LLM Gateway team, and only then in effect.
A filing covers input, output, optional cached-input, and optional per-request prices (USD per token, decimal or exponent notation), plus a note for the reviewer. It can also carry **regional fares** — per-region price overrides developers reach with `/:`, while all other traffic pays the default fares. Each approved filing's regional set fully replaces the previous one, and while no filing is pending a region can be dropped from the Fleet page — removing an offering changes no price, so it applies immediately without review. The latest approved filing is the model's effective price; pending and rejected filings are tracked under **Filings**.
## Routing and billing [#routing-and-billing]
Approved listings are genuinely routable. A request for `/` that does not match the static catalogue resolves against active Airside listings with an approved price filing, and is billed at the filed prices — no extra configuration on the developer side.
## Traffic [#traffic]
The **Traffic** page shows usage of your claimed providers: requests, errors, input/output/total tokens, and billed traffic in USD, as a daily series and broken down per model.
Chart axes and compact counts use `k`, `M`, `B`, or `T` for thousands through trillions, so large traffic volumes fit within the chart.
## Incidents [#incidents]
The **Incidents** page lists every mapping of your claimed providers that failed requests in the selected window (1 hour to 3 days), with its error rate, error count, upstream/gateway split, and total requests. Only upstream and gateway errors count: both are retried on another key or provider, and upstream errors count against your uptime. Client errors, canceled requests, and content-filtered requests are excluded.
By default the page counts only traffic served by the gateway's own credentials. Turn on **Bring your own key traffic** to also include requests customers sent with their own provider keys.
Expand a row to see the top error shapes behind it: HTTP status, status text, a response excerpt, the cause, the gateway's classification, and whether the request was streamed. The shapes cover every error in the window, including responses that started successfully and failed midway, up to the latest 100,000 per mapping. Counts always include retried attempts; the **Retried errors in details** switch drops retried attempts from the error shapes only.
Switch to **By error type** to group the same errors across mappings instead: each error shows its total, its streaming and non-streaming counts, and every model it hit with per-model counts. Use it to spot failures such as timeouts that are not specific to one model. The same per-mapping limit applies.
To focus on one mapping, pick it from the mapping filter or open `/dashboard/incidents?mapping=/[:]`. The **Errors** column on Traffic and the **Incidents** link on each active Fleet listing open the page pre-filtered.
## Fares and routing [#fares-and-routing]
Under **Fares** each claimed provider has two knobs:
* **Traffic discount** (0–50%) — a discount you offer on routed traffic.
* **Landing fee** (5–50%) — the gateway margin you accept; the baseline is **20%**.
Accepting more than the baseline margin or offering a discount makes your traffic effectively cheaper, which boosts you in the [smart routing election](https://docs.llmgateway.io/features/routing); accepting less prices you up. Like price changes, fare changes are filed for review and only reach routing once the LLM Gateway team approves them. The election scores every candidate provider on weighted factors — expected token cost after your discount and margin (`0.6`), availability/uptime (`0.5`), throughput (`0.3`), and latency (`0.075`) — and the lowest score wins. Cache-read savings count toward token cost; cache support alone receives no additional preference by default.
Routing reacts to live metrics: reliability and speed still matter. A cheap
but flaky deployment loses the election to a slightly pricier, stable one.
## Settings [#settings]
**Settings** holds the preflight **test key** for each claimed carrier, shown masked. Replace or remove it at any time; without one, preflight asks for a key on every run.
Settings also shows each carrier's **billing mode**, which the LLM Gateway team sets and you can only view:
* **Pay-as-you-go** (default) — LLM Gateway prepays usage on its own account with you.
* **Post-billing** — you invoice LLM Gateway after each month's usage, paid by wire.
* **Payout** — you supply the API key and cover upstream costs; LLM Gateway pays you after each month.
Contact the LLM Gateway team to change it.
## Provider resources [#provider-resources]
The [Airside resources](https://airside.llmgateway.io/resources) hub collects provider guides ([how to list your LLM API](https://airside.llmgateway.io/guides/list-your-llm-api), [LLM inference pricing](https://airside.llmgateway.io/guides/llm-inference-pricing)) and free calculators for [token costs](https://airside.llmgateway.io/tools/token-cost-calculator) and [rate limits](https://airside.llmgateway.io/tools/rate-limit-calculator). They run in the browser and need no account.
## Carrier Slack channel [#carrier-slack-channel]
Every new carrier is invited to a shared cross-team Slack Connect channel with the LLM Gateway team — the fastest way to reach us about claims, filings, or routing.
# Airside Fleet
URL: https://docs.llmgateway.io/learn/airside-fleet
The Fleet page is where a claimed carrier manages its model listings on
[Airside](https://docs.llmgateway.io/features/airside), the self-serve provider console. Each listing is
a "flight strip" showing its status, its currently approved pricing, and any
tariff filing still awaiting review.
## Listing statuses [#listing-statuses]
| Status | Meaning |
| ----------------------------------- | ------------------------------------------------------------------------- |
| **Filed** | Drafted — waiting for the initial price filing to be approved |
| **In service** | Approved and routable as `/`; billed at the filed prices |
| **Paused** | In service but taken out of routing by you until resumed |
| **Rejected** | The initial filing was rejected; fix the price and file again |
| **Delisted** | Out of service — kept in history; relist it to route traffic again |
| **Awaiting clearance / Fare filed** | A filing (initial or price update) is pending review |
## Registering an aircraft [#registering-an-aircraft]
Click **Register aircraft**, pick the carrier (when your company holds more
than one), and fill in the model id, display name, family, context size, capabilities,
accepted `tool_choice` modes, optional quantization, and the initial tariff.
For a model already in the catalogue, **Use catalog price** copies another
provider's flat prices as a starting point. Prices are per token and accept exponent notation —
`2e-6` reads as $2 per million tokens.
New listings and every later price change are reviewed by the LLM Gateway team
before taking effect. Drafts save immediately; on a live listing, metadata
changes such as description, context size, capabilities, or quantization are
filed for review too.
Every new listing must pass preflight before it is filed. So must an edit that
adds a capability, raises the context size or output limit, or adds accepted
modes or reasoning tiers: run **Run preflight** in the edit dialog and save once
it passes. Narrowing a capability or editing descriptive fields needs no new
run.
## Changing a price [#changing-a-price]
Pricing is never edited in place. **File fare** opens a tariff filing with the
new input/output prices; the model keeps serving at its current approved
pricing until the filing is approved. Only one filing per model can be pending
at a time, and every filing — with its review outcome and notes — is listed in
the console's filings history.
## Pausing a model [#pausing-a-model]
**Pause** takes an in-service model out of routing without review: it stops
receiving new requests within about a minute and keeps its approved pricing,
pending filings, and history. **Resume** puts it back in service the same way.
Filings approved while paused apply without resuming the model.
A paused model is hidden from the public model pages and `/v1/models` until
you resume it. This also holds for a model imported from the built-in
catalogue: requests do not fall back to the catalogue entry.
## Removing a model [#removing-a-model]
Deleting a drafted or rejected listing removes it outright. An in-service
model is **delisted** instead: it stops routing immediately and leaves the
public model pages, but stays in your history and analytics. A model imported
from the built-in catalogue is delisted the same way, without falling back to
its catalogue entry.
Delisting takes effect within about a minute — in-flight requests finish, but
new requests for the model are rejected.
## Relisting a model [#relisting-a-model]
A delisted row shows who took it out of service and when: **Delisted by your
crew** or **Delisted · claim revoked**. Run **Verify** first: **Relist** needs a
preflight that passed after the model was delisted, then puts it back in service
immediately at its last approved fare, without review. Relisting also needs an
active claim on the carrier, and fails while another listing uses the same
model id. Filings that were pending at delisting stay rejected, and
model-level dispatch settings restart from the carrier default.
# DevPass Usage API
URL: https://docs.llmgateway.io/developers/devpass-usage
DevPass subscription prices stay the same, but included usage decreases. Daily
and premium weekly caps and Reset Pass benefits change on October 15; existing
monthly allowances change at the first renewal on or after that date. New
subscriptions from that date start with the new allowance. Review
the [dated plan-change
notice](https://devpass.llmgateway.io/legal/terms#october-2026-plan-changes)
before subscribing.
Use `GET /v1/key` to show DevPass allowance meters in a coding tool or other
trusted application. The endpoint uses the application's existing gateway API
key, so it does not require a dashboard session.
A regular gateway API key is a secret. Do not embed it in public browser code
or distribute it with an application. Read it from the user's secure local
configuration or call the endpoint from a trusted backend.
## Request [#request]
```bash
curl https://api.llmgateway.io/v1/key \
-H "Authorization: Bearer llmgtwy_your_api_key_here"
```
Only regular gateway API keys can use this endpoint. Platform publishable keys
and end-user sessions receive `403 Forbidden` because they cannot read
organization-level plan state.
## Response [#response]
```json
{
"data": {
"label": "My coding tool",
"usage": "31.42",
"limit": null,
"devPlan": "pro",
"devPlanCreditsUsed": "25",
"devPlanCreditsLimit": "237",
"devPlanCreditsRemaining": "212.00",
"devPlanPremiumWeeklyLimit": "35.55",
"devPlanPremiumCreditsUsed": "5.00",
"devPlanPremiumWeekResetsAt": "2026-08-28T12:00:00.000Z"
}
}
```
| Field | Meaning |
| ---------------------------- | -------------------------------------------------------------- |
| `label` | Description of the key |
| `usage` | All-time usage attributed to the key, in USD |
| `limit` | All-time key usage limit in USD, or `null` |
| `devPlan` | `lite`, `pro`, `max`, or `none` |
| `devPlanCreditsUsed` | Plan credits used in the current billing cycle |
| `devPlanCreditsLimit` | Plan credit allowance for the current billing cycle |
| `devPlanCreditsRemaining` | Remaining plan credits, clamped to zero |
| `devPlanPremiumWeeklyLimit` | Weekly premium-model allowance |
| `devPlanPremiumCreditsUsed` | Premium-model credits used in the current weekly window |
| `devPlanPremiumWeekResetsAt` | ISO 8601 reset time, or `null` when no weekly window is active |
All USD values are decimal strings. Parse them before calculating percentages
or remaining premium allowance.
## Weekly window behavior [#weekly-window-behavior]
The premium window starts with the first premium-model request and lasts seven
days. When it expires, the endpoint returns `"0.00"` for
`devPlanPremiumCreditsUsed` and `null` for `devPlanPremiumWeekResetsAt`; the
full weekly allowance is already available, and the next premium request starts
a new window.
Pay-as-you-go keys return `"none"` for `devPlan` and zero for every DevPass
field. This lets one integration support both key types without a separate
account lookup.
The endpoint remains readable when the key has exceeded its own usage limit,
so an integration can explain why requests stopped. Invalid or inactive keys
receive `401 Unauthorized`.
See [Retrieve key status](https://docs.llmgateway.io/v1_key_retrieve) for the generated API reference and
complete response schema.
## Recovering a failed renewal [#recovering-a-failed-renewal]
If your renewal fails, DevPass shows **Payment overdue** with a **Complete payment**
link to the outstanding invoice. Follow the link to pay and complete any bank
verification. Your allowance refreshes after payment succeeds.
Updating your card in **Billing** also retries the failed renewal. Saving a card
alone does not mean the invoice is paid: if payment remains outstanding, the
invoice link stays visible above the card details. Return to DevPass after paying
to refresh the payment status.
# Agent Skills
URL: https://docs.llmgateway.io/guides/agent-skills
**Agent Skills** are structured guidelines for AI coding agents, optimized for use with LLM Gateway and the AI SDK. They provide best practices and reusable instructions that help AI agents generate higher-quality code.
## Video walkthrough [#video-walkthrough]
## What Are Agent Skills? [#what-are-agent-skills]
Agent Skills are packaged sets of rules and guidelines that teach AI coding agents how to implement specific features correctly. Each skill covers:
* API integration patterns
* Frontend rendering best practices
* Error handling strategies
* Performance optimization techniques
## Available Skills [#available-skills]
### Image Generation [#image-generation]
The Image Generation skill teaches AI agents how to properly implement image generation features:
* **API Integration** — correctly calling image generation APIs
* **Frontend Rendering** — displaying generated images efficiently
* **Error Handling** — graceful degradation and retry logic
* **Performance** — caching, lazy loading, and optimization
### Changelog Writer [#changelog-writer]
The Changelog Writer skill drafts a polished LLM Gateway changelog entry and its OpenGraph image in one pass:
* **House style** — writes in the established changelog voice: problem first, benefits over features, plain and confident
* **Correct format** — generates the dated Markdown file with valid frontmatter (`id`, `slug`, `date`, `title`, `summary`, `tags`, `image`)
* **OG image prompt** — hands back a ready-to-run `gpt-image-2` prompt at the recommended **1536×1024** OpenGraph resolution
* **Validation** — formats and builds the entry before you commit
It ships as a [Claude Code skill](https://docs.claude.com/en/docs/claude-code/skills) — a single `SKILL.md` file. Trigger it by typing `changelog` (or "write a changelog entry") in Claude Code.
## Claude Code Skills [#claude-code-skills]
Some skills ship as **[Claude Code skills](https://docs.claude.com/en/docs/claude-code/skills)**: a single `SKILL.md` file with YAML frontmatter that Claude Code loads automatically when its description matches what you ask. The **Changelog Writer** above is one of them.
Install one by copying its folder into your project's `.claude/skills/` directory:
```bash
# from the llmgateway-templates repo
git clone https://github.com/theopenco/llmgateway-templates.git
mkdir -p .claude/skills
cp -R llmgateway-templates/skills/changelog .claude/skills/
```
Run these commands from your project directory. Review the installed `.claude/skills/changelog/SKILL.md`, then open Claude Code in that project and trigger it by name:
```
> changelog
```
Place the folder in `~/.claude/skills/` instead to make the skill available across every project. Browse the available Claude Code skills in the [llmgateway-templates repository](https://github.com/theopenco/llmgateway-templates/tree/main/skills).
## Build the image-generation instructions [#build-the-image-generation-instructions]
### Prerequisites [#prerequisites]
Ensure you have Node.js 18+ and pnpm 9+ installed:
```bash
node --version # v18.0.0 or higher
pnpm --version # 9.0.0 or higher
```
### Clone the Repository [#clone-the-repository]
```bash
git clone https://github.com/theopenco/agent-skills.git
cd agent-skills
```
### Install Dependencies [#install-dependencies]
```bash
pnpm install
```
### Build Skills [#build-skills]
Build all skills to generate the documentation:
```bash
pnpm build:all
```
Or build a specific skill:
```bash
pnpm build
```
## Use the generated image-generation instructions [#use-the-generated-image-generation-instructions]
After building, each skill generates an `AGENTS.md` file that can be used with AI coding agents like Claude, Cursor, or Copilot.
### With Claude Code [#with-claude-code]
Add the generated `AGENTS.md` content to your project's `CLAUDE.md` file:
```bash
cat skills/image-generation/AGENTS.md >> CLAUDE.md
```
### With Cursor [#with-cursor]
Add the skill content to your `.cursorrules` file:
```bash
cat skills/image-generation/AGENTS.md >> .cursorrules
```
### With Other AI Agents [#with-other-ai-agents]
Most AI coding tools support custom instructions. Copy the skill content into your tool's configuration.
## Project Structure [#project-structure]
```
agent-skills/
├── packages/
│ └── skills-build/ # Build tooling
├── skills/
│ └── image-generation/ # Individual skill
│ ├── rules/ # Rule files
│ ├── AGENTS.md # Generated documentation
│ └── metadata.json # Skill metadata
└── package.json
```
## Contributing [#contributing]
### Adding New Rules [#adding-new-rules]
### Fork and Clone [#fork-and-clone]
Fork the repository and create a feature branch:
```bash
git checkout -b feat/new-rule
```
### Create a Rule File [#create-a-rule-file]
Rules follow a standardized template with YAML frontmatter containing `title`, `impact` (high/medium/low), and `tags`. The body includes sections for Context, Incorrect examples, and Correct examples with TypeScript code blocks.
See existing rules in `skills/image-generation/rules/` for reference.
### Validate and Build [#validate-and-build]
```bash
pnpm validate
pnpm build:all
```
### Submit a Pull Request [#submit-a-pull-request]
Push your changes and open a PR.
### Impact Levels [#impact-levels]
When creating rules, use these impact levels:
* **high** — Critical for correctness or security
* **medium** — Important for quality and maintainability
* **low** — Nice-to-have improvements
## Development Commands [#development-commands]
| Command | Description |
| ---------------- | --------------------------- |
| `pnpm install` | Install dependencies |
| `pnpm build:all` | Build all skills |
| `pnpm build` | Build a specific skill |
| `pnpm validate` | Validate rule files |
| `pnpm dev` | Development mode with watch |
## More Resources [#more-resources]
* [GitHub Repository](https://github.com/theopenco/agent-skills) — Source code and contributions
* [LLM Gateway CLI](https://docs.llmgateway.io/developers/cli) — Project scaffolding tool
* [Templates](https://llmgateway.io/templates) — Production-ready starter projects
Want to contribute a new skill or rule? Check out the [contribution
guidelines](https://github.com/theopenco/agent-skills#contributing) on GitHub.
# Anvil Integration
URL: https://llmgateway.io/guides/anvil
[Anvil](https://anvilstack.dev) is a desktop workspace for repository-based agent work. Its built-in LLM Gateway connector supports browser authorization and API keys.
## Video walkthrough [#video-walkthrough]
## Install Anvil [#install-anvil]
Download the desktop app from the [official releases](https://github.com/anthonyhumphreys/anvil-stack/releases). On first launch, choose your role and open **Primary agent**. You can also configure providers later in **Settings**.
## Connect LLM Gateway [#connect-llm-gateway]
Select **LLMGateway**, then choose the billing mode matching your account:
| Mode | Use it with |
| ------------- | --------------------------------------------------------- |
| DevPass | A coding plan and canonical models supported by that plan |
| Pay as you go | A workspace billed against gateway credits |
Click **Connect in browser**. Review the account and organization on the authorization page, then approve the connection while Anvil stays open. Alternatively, paste an existing key into **API key (alternative)**.
The selected mode determines which model catalogue Anvil loads. Keep DevPass models canonical; provider-pinned routing is not available on coding plans.
## Check the coding engine [#check-the-coding-engine]
Anvil uses a local Codex coding engine to edit files and run commands. Models and billing still come through LLM Gateway; this connector does not require a separate ChatGPT account.
If Anvil reports that the engine is missing or unavailable, use its **Install coding engine** or **Repair coding engine** control, then check the status again.
## Select a model and test [#select-a-model-and-test]
After connecting, choose a tool-capable model from the validated catalogue and click **Test Connection**. Check capabilities on the [models page](https://llmgateway.io/models?features=tools).
Start a small task in your repository, review the proposed changes, and run the relevant tests. Inspect usage in the gateway workspace you authorized.
## Troubleshooting [#troubleshooting]
**Browser authorization times out:** start the connection again and finish the approval while Anvil remains open.
**No models appear:** check the connected key and billing mode, then refresh the connector status.
**The coding engine is unavailable:** use the install or repair control before starting an agent task.
**Authentication fails:** verify that the key is active, or reconnect in the browser to authorize a new connection.
# Autohand Code Integration
URL: https://llmgateway.io/guides/autohand
[Autohand Code](https://autohand.ai) is a terminal coding agent with a built-in LLM Gateway provider. Use a pay-as-you-go or DevPass API key to connect it to your workspace.
## Video walkthrough [#video-walkthrough]
## Install [#install]
```bash
pnpm add -g autohand-cli
autohand --version
```
See the [Autohand documentation](https://docs.autohand.ai/getting-started/first-session) for installation and onboarding.
## Sign in to Autohand [#sign-in-to-autohand]
The current CLI requires an Autohand account even when inference uses a provider API key. Run `autohand login` and complete the browser sign-in before starting a session. Your LLM Gateway key is a separate credential.
## Connect LLM Gateway [#connect-llm-gateway]
Create a key in your [dashboard](https://llmgateway.io/dashboard), then export it before launching Autohand:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
autohand --provider llmgateway --model MODEL_ID
```
Replace `MODEL_ID` with a text model that supports tools from the [live catalogue](https://llmgateway.io/models?features=tools). With DevPass, choose a canonical model ID supported by your plan; provider-pinned routing is unavailable on coding plans.
## Save the provider configuration [#save-the-provider-configuration]
To keep LLM Gateway as your default, merge these settings into `~/.autohand/config.json`:
```json
{
"provider": "llmgateway",
"llmgateway": {
"apiKey": "${LLMGATEWAY_API_KEY}",
"baseUrl": "https://api.llmgateway.io/v1",
"model": "MODEL_ID"
}
}
```
The provider name is a string, with a separate `llmgateway` settings object. Keep the API key in your environment and replace the model placeholder before running the agent.
## Verify a coding task [#verify-a-coding-task]
Start `autohand` from your project directory. Ask it to read a failing test, fix the implementation, and rerun the test without changing the test file. Review its file changes and command output, then check the request in the workspace that issued your key.
## Troubleshooting [#troubleshooting]
* **Wrong provider:** Pass `--provider llmgateway` explicitly or check the top-level `provider` setting.
* **Authentication failed:** Export `LLMGATEWAY_API_KEY` in the same shell and confirm the key is active.
* **Model unavailable:** Choose a tool-capable model allowed by your workspace or DevPass plan.
See Autohand's [LLM Gateway integration reference](https://docs.autohand.ai/integrations/llmgateway) for additional options.
# Claude Code Integration
URL: https://llmgateway.io/guides/claude-code
Claude Code can use LLM Gateway's Anthropic-compatible endpoint while the gateway routes requests to your selected model. This walkthrough was verified with Claude Code 2.1.263.
## Video walkthrough [#video-walkthrough]
## Install [#install]
Install Claude Code using the [official instructions](https://code.claude.com/docs/en/setup), then check `claude --version`.
## Configure the connection [#configure-the-connection]
Create a key in your [dashboard](https://llmgateway.io/dashboard), then set these variables in the terminal where you launch Claude Code:
```bash
export ANTHROPIC_BASE_URL=https://api.llmgateway.io
export ANTHROPIC_AUTH_TOKEN="your_api_key"
export ANTHROPIC_MODEL=deepseek-v4-flash
claude
```
Use the base URL exactly as shown. Claude Code adds `/v1/messages` itself. If an existing `ANTHROPIC_API_KEY` causes an authentication conflict, unset it in this shell before starting.
The example model supports the tool calls needed to read, edit, and test code. Find other compatible choices in the [live model catalogue](https://llmgateway.io/models?features=tools).
## Choose a model [#choose-a-model]
Set `ANTHROPIC_MODEL` before launch, or override it for one session:
```bash
claude --model deepseek-v4-flash
```
Claude Code's picker and model-discovery behavior vary by version. An explicit `--model` value is useful when your model is absent from the picker.
To add a custom picker option, current Claude Code also supports:
```bash
export ANTHROPIC_CUSTOM_MODEL_OPTION=deepseek-v4-flash
export ANTHROPIC_CUSTOM_MODEL_OPTION_NAME="DeepSeek V4 Flash"
```
To discover the gateway models supported by Claude Code's picker, set `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1` before launch. Discovery is subject to the client's model filtering; it does not guarantee that every gateway model appears.
## Verify the connection [#verify-the-connection]
Open a small project and ask the agent to read a file, make a change, and run its tests. Our recorded example fixes a TypeScript slugifier and passes all three tests with `deepseek-v4-flash` through LLM Gateway.
`Hello, LLM Gateway!` becomes `hello-llm-gateway`; repeated separators collapse into one hyphen; empty and punctuation-only inputs stay empty. The demo uses Node.js 24 to run `node --test slugify.test.ts` directly.
Review the diff and test output, then check the request in your [LLM Gateway dashboard](https://llmgateway.io/dashboard). Choose other compatible models from the [live catalogue](https://llmgateway.io/models?features=tools).
## Prompt cache lifetime [#prompt-cache-lifetime]
For Claude models, the default 5-minute cache refreshes on each hit. If coding pauses or long tool runs regularly exceed five minutes, opt into a 1-hour lifetime before starting Claude Code:
```bash
export CLAUDE_CODE_PROMPT_CACHE_TTL=1h
```
One-hour cache writes cost more, so the default is cheaper for continuous work. `FORCE_PROMPT_CACHING_5M=1` overrides this setting. See [provider cache control](https://docs.llmgateway.io/features/caching/provider-cache-control) for pricing and usage fields.
## Auto mode classifier billing [#auto-mode-classifier-billing]
When Claude Code requests server-side auto mode review, routing is restricted to
providers that can forward it. This also applies to DevPass with canonical model
IDs; no provider prefix is needed. An incompatible saved session pin is replaced,
and requests with no compatible provider are rejected instead of silently routing
to one that would drop the review.
Check `/status` for **Auto mode server: Enabled**. Availability still depends on
the upstream model and credentials. Set `CLAUDE_CODE_AUTO_MODE_SERVER=0` before
launch to opt out and allow broader routing with billed client-side classifier
requests. See [auto mode safeguards](https://docs.llmgateway.io/features/anthropic-endpoint#auto-mode-safeguard-review).
## Troubleshooting [#troubleshooting]
* **Requests use the wrong endpoint:** check `ANTHROPIC_BASE_URL` in the shell that starts Claude Code and restart the agent after changing it.
* **Authentication fails:** verify the gateway key and resolve conflicting Anthropic authentication variables.
* **A model is missing from the picker:** launch with an explicit `--model` ID from the catalogue.
* **An optional tool is unsupported:** use a model with the required capability, or disable the tool for that session.
A [DevPass](https://devpass.llmgateway.io) plan key can also be used. Use canonical model IDs for plan routing.
# Cline Integration
URL: https://llmgateway.io/guides/cline
[Cline](https://cline.bot) is a coding agent for VS Code. Configure its OpenAI-compatible provider to send model requests through LLM Gateway with a pay-as-you-go or DevPass key.
## Video walkthrough [#video-walkthrough]
## Install Cline [#install-cline]
Open the VS Code Extensions view with **Cmd/Ctrl + Shift + X**, search for **Cline**, and install the extension published by Cline.
## Connect LLM Gateway [#connect-llm-gateway]
On first launch, choose **Bring my own API key** and continue. For an existing installation, open Cline's settings using the gear icon.
Set these fields:
| Field | Value |
| ------------ | ------------------------------------------------ |
| API Provider | `OpenAI Compatible` |
| Base URL | `https://api.llmgateway.io/v1` |
| API Key | A key from your LLM Gateway or DevPass dashboard |
| Model ID | A tool-capable model ID from the live catalogue |
Create a key in your [dashboard](https://llmgateway.io/dashboard). Find model IDs, context limits, and capabilities on the [models page](https://llmgateway.io/models?features=tools).
Cline's model configuration also lets you set context and capability information. Match those settings to your selected model. A default or zero cost shown inside Cline is not the gateway's bill; check actual usage in your dashboard.
> **Using DevPass?** Use a canonical model ID included in your plan, without an upstream provider prefix. The gateway selects the provider. See the [DevPass site](https://devpass.llmgateway.io).
## Verify a small task [#verify-a-small-task]
Open a project and ask Cline to fix one function with an existing test. **Plan** mode can inspect the project and propose a change. Switch to **Act** to apply it.
Review the proposed diff before approving the edit. Approve the test command when prompted, then inspect its output. Keep the scope small until you are comfortable with the tool permissions.
Cline can keep separate models for Plan and Act. If you enable that option, configure both modes to use the intended gateway endpoint and key.
## Troubleshooting [#troubleshooting]
**Authentication fails:** confirm the key is active and the base URL includes `/v1`.
**A model is unavailable:** copy its exact ID from the live catalogue and check that your account can use it.
**Cline plans without editing:** switch to Act mode and approve the proposed edit.
**Terminal output is missing:** check VS Code shell integration. Interactive shell startup prompts can interrupt commands; dismiss them or use a terminal profile with a clean startup, then rerun the test.
**The context limit is wrong:** match Cline's model configuration to the model's documented context window and start a fresh task if the conversation is already too large.
# Codex CLI Integration
URL: https://llmgateway.io/guides/codex-cli
[Codex CLI](https://github.com/openai/codex) can connect to LLM Gateway through a custom provider using the Responses API. This walkthrough was verified with Codex CLI 0.153.4.
## Video walkthrough [#video-walkthrough]
## Install [#install]
```bash
pnpm add -g @openai/codex
codex --version
```
## Configure LLM Gateway [#configure-llm-gateway]
Create a key in your [dashboard](https://llmgateway.io/dashboard) and export it in the terminal where you launch Codex:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Merge these settings into `~/.codex/config.toml`:
```toml
model = "deepseek-v4-flash"
model_provider = "llmgateway"
model_reasoning_effort = "low"
web_search = "disabled"
[model_providers.llmgateway]
name = "LLM Gateway"
base_url = "https://api.llmgateway.io/v1"
env_key = "LLMGATEWAY_API_KEY"
wire_api = "responses"
```
The named provider reads your gateway key from the environment. You can keep your existing ChatGPT login. Current Codex versions use `responses`; changing an environment variable cannot switch them to Chat Completions.
Web search is disabled for this example because the selected model does not support native web search. Enable it only after selecting a model with that capability in the [catalogue](https://llmgateway.io/models?features=webSearch).
## Start coding [#start-coding]
```bash
cd your-project
codex
```
For a scripted task:
```bash
codex exec --sandbox workspace-write "Fix the failing test and run it again"
```
## Verify the connection [#verify-the-connection]
Open a small project and ask the agent to read a file, make a change, and run its tests. Our recorded example fixes a TypeScript slugifier and passes all three tests with `deepseek-v4-flash` through LLM Gateway.
`Hello, LLM Gateway!` becomes `hello-llm-gateway`; repeated separators collapse into one hyphen; empty and punctuation-only inputs stay empty. The demo uses Node.js 24 to run `node --test slugify.test.ts` directly.
Review the diff and test output, then check the request in your [LLM Gateway dashboard](https://llmgateway.io/dashboard). Choose other compatible models from the [live catalogue](https://llmgateway.io/models?features=tools).
## Troubleshooting [#troubleshooting]
* **Authentication fails:** check that `model_provider` matches the provider table and that `LLMGATEWAY_API_KEY` is exported in the same shell.
* **Native web search is unsupported:** set `web_search = "disabled"`, then start a new session.
* **Model metadata or catalogue refresh warning:** Codex 0.153.4 can warn about missing metadata or the gateway's model-list response format. Our configured model still completed the task. Use an explicit model ID and verify the actual request result.
* **Wrong endpoint:** the provider's `base_url` must end in `/v1`.
See OpenAI's [custom-provider configuration](https://developers.openai.com/codex/config-advanced/) for configuration profiles and other options.
# Conductor Integration
URL: https://llmgateway.io/guides/conductor
[Conductor](https://www.conductor.build) runs coding agents in separate workspaces. Its bundled OpenCode agent includes an LLM Gateway provider, so you can connect from Settings without installing a separate CLI or entering a base URL.
Verified with Conductor **0.87.4**.
## Prerequisites [#prerequisites]
* Install Conductor from the [official site](https://www.conductor.build).
* Create an [LLM Gateway API key](https://docs.llmgateway.io/learn/api-keys), or use a [DevPass](https://docs.llmgateway.io/devpass) plan key.
* Open a repository and workspace in Conductor.
## Connect LLM Gateway [#connect-llm-gateway]
1. Open **Settings → Agents → OpenCode**. Some versions label **Agents** as **Harnesses**.
2. Under **Providers**, click **Configure**.
3. If LLM Gateway is missing, click **View all providers**, then search for **LLM Gateway**.
4. Expand **LLM Gateway**, paste your API key into **LLMGATEWAY\_API\_KEY**, and click **Save**. A checkmark indicates that a key is configured.
The built-in provider supplies the gateway endpoint. Use your pay-as-you-go key for usage-based billing or your DevPass key for your coding plan.
## Choose a model [#choose-a-model]
1. Close the provider dialog and click **Configure** under **Models**.
2. Search for a model and check its entry under **llmgateway**. Choose a model with tool calling from the [live catalogue](https://llmgateway.io/models?features=tools).
For DevPass, choose a model included in your plan under **llmgateway**. The **llmgateway-providers** entries pin an upstream provider and are not supported on coding plans. The model shown in the screenshot is an example; availability changes with the catalogue and your account.
3. Open **Settings → Default models** and add your model from the **OpenCode** column to your **Loadout**. To change an existing slot, open its menu, choose **Agent → OpenCode**, then select your model under **Model**.
4. Return to your workspace, open a new chat, and choose that model from the agent picker below the message box. The picker's **Edit** button takes you back to **Default models**.
## Verify the connection [#verify-the-connection]
Send a small prompt first:
```text
Reply with exactly: Hello from LLM Gateway! Do not use tools or read or change any files.
```
In your [LLM Gateway dashboard](https://llmgateway.io/dashboard), select the project that issued the key and open **Activity** from the sidebar to inspect the request. DevPass users can review usage under [Coding Agents](https://docs.llmgateway.io/learn/coding-agents); Conductor runs these requests through OpenCode.
Once connected, ask the agent to make a small change and run the relevant tests. Review the tool output and workspace diff before keeping the change.
## Troubleshooting [#troubleshooting]
**LLM Gateway is missing:** click **View all providers** and search again. If it still does not appear, update Conductor.
**The model is missing from the chat picker:** enable it under **Agents → OpenCode → Models**, then add it to **Default models → Loadout**.
**Authentication fails:** check that the provider key was saved and is active in the matching dashboard.
**Requests use another provider:** select the model under **llmgateway**, then confirm that the chat is using your OpenCode loadout entry.
For executable overrides and other provider configuration options, see Conductor's [OpenCode documentation](https://www.conductor.build/docs/reference/harnesses/opencode).
# Continue CLI Integration
URL: https://llmgateway.io/guides/continue
[Continue CLI](https://docs.continue.dev/cli/quickstart) runs a coding assistant in your terminal. Connect its OpenAI-compatible provider to LLM Gateway with a pay-as-you-go or DevPass API key.
## Video walkthrough [#video-walkthrough]
## Install [#install]
```bash
pnpm add -g @continuedev/cli
cn --version
```
## Configure the model [#configure-the-model]
Create an API key in your [dashboard](https://llmgateway.io/dashboard). Save this configuration in `~/.continue/config.yaml`, merging it with any settings you already use:
```yaml
name: LLM Gateway
version: 1.0.0
schema: v1
models:
- name: Gateway coding model
provider: openai
model: MODEL_ID
apiBase: https://api.llmgateway.io/v1
apiKey: ${{ secrets.LLMGATEWAY_API_KEY }}
capabilities:
- tool_use
```
Replace `MODEL_ID` with a text model that supports tools from the [live catalogue](https://llmgateway.io/models?features=tools), and export your key in the terminal where you launch Continue:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Keep the key private. `capabilities: [tool_use]` tells Continue to enable tool calling; the selected model must actually support it.
`provider: openai` selects the API format. LLM Gateway routes the request to the serving provider. With DevPass, use a canonical model ID supported by your plan, without an upstream provider prefix.
## Start a session [#start-a-session]
Launch Continue from your project with the configuration selected explicitly:
```bash
cd your-project
cn --config ~/.continue/config.yaml
```
Ask it to fix a small failing test, leaving the test itself unchanged. Review requested tool permissions, inspect the diff, and confirm the test output. Requests appear in the dashboard for the workspace that issued your key.
For a one-shot prompt:
```bash
cn --config ~/.continue/config.yaml -p "Explain this project without changing files"
```
## Change models [#change-models]
Add another entry under `models` with a different name and model ID, keeping the gateway endpoint. Select the configured model in Continue. Check the live catalogue for each model's tool and image capabilities.
A separate project configuration works too: pass its path to `--config` when launching the CLI.
## Troubleshooting [#troubleshooting]
* **Configuration rejected:** Include `name`, `version`, and `schema: v1` at the top level, and check YAML indentation.
* **Onboarding appears instead:** Pass the intended file with `--config`.
* **Authentication failed:** Check the key and workspace in the dashboard.
* **Request finishes without visible text:** Continue CLI 1.5.47 drops text chunks that also contain usage, which can affect Gemini responses through the gateway. Choose another tool-capable model and check for a Continue update.
* **Tool calling unavailable:** Choose a model with tool support and include `tool_use` in its capabilities.
* **Connection failed:** Use `https://api.llmgateway.io/v1` as `apiBase`.
See the [Continue configuration reference](https://docs.continue.dev/reference) for additional options.
# Crush Integration
URL: https://llmgateway.io/guides/crush
[Crush](https://github.com/charmbracelet/crush) is a terminal coding agent. Connect its OpenAI-compatible provider to LLM Gateway with a pay-as-you-go or DevPass key.
## Video walkthrough [#video-walkthrough]
## Install [#install]
```bash
pnpm add -g @charmland/crush
crush --version
```
Other installation options are in the [Crush documentation](https://github.com/charmbracelet/crush#installation).
## Connect LLM Gateway [#connect-llm-gateway]
Create an API key in your [dashboard](https://llmgateway.io/dashboard), then export it in the terminal where you run Crush:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Add this provider to your project's `crush.json`, merging it with existing settings:
```json
{
"providers": {
"llmgateway": {
"name": "LLM Gateway",
"type": "openai",
"base_url": "https://api.llmgateway.io/v1",
"api_key": "$LLMGATEWAY_API_KEY"
}
}
}
```
The key stays in your environment. Crush discovers the available models from the gateway when no explicit model list is configured.
## Choose a model and code [#choose-a-model-and-code]
```bash
cd your-project
crush
```
Select **LLM Gateway** and a model with tool support from the [live catalogue](https://llmgateway.io/models?features=tools). With DevPass, choose a canonical model ID supported by your plan, without an upstream provider prefix.
Start with a small task that has a clear test. Review tool permission requests, inspect the changes, and run the tests. Check usage in the workspace that issued your API key.
## Troubleshooting [#troubleshooting]
* **Authentication failed:** Check that `LLMGATEWAY_API_KEY` is exported in the same terminal and the key is active.
* **Models missing:** Restart Crush after editing the provider configuration. Check the endpoint and key before adding a manual model list.
* **Tool calls fail:** Confirm the selected model supports tools.
* **Provider unavailable:** Gateway fallback can route requests to another eligible provider. See the [routing documentation](https://docs.llmgateway.io/features/routing) for pinning and fallback controls.
See the [Crush configuration reference](https://github.com/charmbracelet/crush/tree/main/docs/config) for permissions and additional settings.
# Cursor Integration
URL: https://llmgateway.io/guides/cursor
Cursor can use a custom OpenAI base URL for supported chat models. Connect LLM Gateway in the editor's model settings, then verify the selected model with a small request.
## Video walkthrough [#video-walkthrough]
## Sign in and open model settings [#sign-in-and-open-model-settings]
Install Cursor from its [official site](https://cursor.com/downloads), sign in to your Cursor account, and open **Cursor Settings → Models → API Keys**.
Cursor account access and your gateway API key are separate requirements.
## Add the gateway connection [#add-the-gateway-connection]
1. Enter your workspace key under **OpenAI API Key**.
2. Enable **Override OpenAI Base URL** and set `https://api.llmgateway.io/v1`.
3. Save or verify the key using the controls in your Cursor version.
4. Select a supported chat model. If your version offers custom model entry, use the exact ID from the [live catalogue](https://llmgateway.io/models).
With a [DevPass](https://devpass.llmgateway.io) key, choose a canonical model included in your plan. Upstream provider prefixes pin routing and are not supported on coding plans.
## Verify the request [#verify-the-request]
Open a new chat, select the configured model explicitly, and send a short prompt. Check the request in the workspace that issued the gateway key before relying on the connection for agent tasks.
Compatibility depends on Cursor's model support and the mode you use. A custom endpoint does not guarantee that every catalogue model or agent feature works.
## Scope and billing [#scope-and-billing]
Cursor documents custom keys for chat models; Tab completion continues using Cursor's built-in models. BYOK requests still pass through Cursor's backend for prompt building. Consult Cursor's [API key documentation](https://cursor.com/help/models-and-usage/api-keys) for current plan charges and data handling.
## Troubleshooting [#troubleshooting]
**Sign-in blocks settings:** complete Cursor account sign-in before configuring the provider.
**Authentication fails:** check the active gateway key and the exact base URL, including `/v1`.
**A model is missing or rejected:** confirm Cursor supports it, check its exact catalogue ID, and test a supported chat model first.
**An agent tool fails:** verify tool support and mode compatibility. Check the actual request and error instead of assuming every editor feature uses the override.
**Tab completion uses Cursor's service:** this is expected; custom chat keys do not replace its completion models.
# DevPass Code Integration
URL: https://llmgateway.io/guides/devpass-code
[DevPass Code](https://github.com/theopenco/devpass-code) is a terminal coding agent built for LLM Gateway. This walkthrough was verified with DevPass Code 1.18.11.
## Video walkthrough [#video-walkthrough]
## Install [#install]
```bash
pnpm add -g devpass-code
devpass-code --version
```
See the [project repository](https://github.com/theopenco/devpass-code) for other installation options.
## Connect LLM Gateway [#connect-llm-gateway]
The recorded walkthrough uses an existing gateway API key:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Create your key in the [dashboard](https://llmgateway.io/dashboard). Alternatively, run `devpass-code auth login`, choose **LLM Gateway** or **LLM Gateway DevPass**, and follow the browser-login or key-entry flow.
Browser login creates a credential and returns it to the tool through a local callback. If you have reached your organization's active-key limit, manage your keys in the dashboard before retrying.
## Choose a model [#choose-a-model]
Place a `devpass-code.json` in your project:
```json
{
"model": "llmgateway/deepseek-v4-flash"
}
```
Then launch the agent:
```bash
cd your-project
devpass-code
```
You can also select the model with `--model llmgateway/deepseek-v4-flash` or use the model picker. Browse the [live catalogue](https://llmgateway.io/models?features=tools) for other coding models.
The `llmgateway/` prefix identifies the agent's provider. The remaining model ID is canonical, which is also the form required for [DevPass](https://devpass.llmgateway.io) plan routing.
## Verify the connection [#verify-the-connection]
Open a small project and ask the agent to read a file, make a change, and run its tests. Our recorded example fixes a TypeScript slugifier and passes all three tests with `deepseek-v4-flash` through LLM Gateway.
`Hello, LLM Gateway!` becomes `hello-llm-gateway`; repeated separators collapse into one hyphen; empty and punctuation-only inputs stay empty. The demo uses Node.js 24 to run `node --test slugify.test.ts` directly.
Review the diff and test output, then check the request in your [LLM Gateway dashboard](https://llmgateway.io/dashboard). Choose other compatible models from the [live catalogue](https://llmgateway.io/models?features=tools).
## Troubleshooting [#troubleshooting]
Check that `LLMGATEWAY_API_KEY` is available to the process and that the model ID matches the picker. If a project opens with an unexpected model, check its `devpass-code.json` and pass `--model` explicitly.
DevPass Code tags requests with `x-source: devpass-code` for attribution in your dashboard.
# Empryo Integration
URL: https://llmgateway.io/guides/empryo
[Empryo](https://empryo.com) is a coding agent with a desktop app, terminal UI, and headless CLI. LLM Gateway is built in to both interactive interfaces. Connect with browser login or an existing API key, then choose a model for your project.
## Video walkthrough [#video-walkthrough]
## Install [#install]
Use the [official installer or release downloads](https://empryo.com/download), then verify the installed version:
```bash
empryo --version
```
## Desktop app (GUI) [#desktop-app-gui]
Download the desktop app from the [Empryo downloads page](https://empryo.com/download), then open a project folder from the workspace selector.
### Connect your account [#connect-your-account]
On first launch, the **Connect a model** screen walks through **Provider**, **Account**, and **Model**:
1. Select **LLM Gateway** as the provider.
2. Choose browser login for pay-as-you-go, or the **DevPass** login option for a coding plan. In an already connected profile, these actions appear as **Reconnect** and **Reconnect · DevPass**.
3. Finish authorization in your browser and return to Empryo. Keep the app running until the callback completes.
4. Choose a tool-capable model and click **Start**.
To use a key instead, enter it in **LLM Gateway API key** and click **Connect**. When both a browser connection and a key exist, the **Use** control chooses **Auto**, **Connection**, or **Key**. Select the credential matching the workspace you intend to bill.
### Work in the desktop app [#work-in-the-desktop-app]
Use the model selector in the top bar to change models. Enter your task in the chat composer, inspect the file changes, and check the test output before accepting the result. The workspace tree and diff viewer let you review what the agent changed alongside the conversation.
## Terminal UI (TUI) [#terminal-ui-tui]
### Log in with your browser [#log-in-with-your-browser]
Start Empryo in your project:
```bash
cd your-project
empryo
```
Type `/login` to connect a pay-as-you-go LLM Gateway account. For a [DevPass](https://devpass.llmgateway.io) subscription, use `/login llmgateway-devpass`.
The agent opens the gateway's authorization page in your browser. Check the account and project, then approve the connection. Approval creates an API key and returns it to Empryo through a local callback. If your organization sets spending limits for you, the key carries the strictest of them. Keep the terminal running until the callback finishes.
You can start the same flow directly from the shell:
```bash
empryo --login llmgateway
# For a DevPass subscription:
empryo --login llmgateway-devpass
```
If the browser reports an active-key limit, revoke a key you no longer use on the API Keys page of the [dashboard](https://llmgateway.io/dashboard), then select **Authorize** again. If the local callback expires, restart login to get a fresh authorization link.
### Choose a model [#choose-a-model]
Open `/models` and select a compatible model under LLM Gateway. The picker reads model information from the gateway; use the [live catalogue](https://llmgateway.io/models?features=tools) to check current capabilities.
The leading `llmgateway/` in Empryo's model selector identifies the agent's provider. For DevPass, keep the remaining model ID canonical; do not pin an upstream provider.
### Use an existing API key [#use-an-existing-api-key]
For scripts or an existing gateway credential, export the key before starting Empryo:
```bash
export LLM_GATEWAY_API_KEY="your_api_key"
empryo
```
Empryo uses `LLM_GATEWAY_API_KEY`, including the underscore between `LLM` and `GATEWAY`. You can also enter a key through `/keys`.
A browser login issues a new credential; it does not reuse a key you already copied from the dashboard.
## Verify the connection [#verify-the-connection]
In either interface, ask the agent to read a file, make a small edit, and run the project's tests. Review both the diff and test output; a completed chat response alone does not prove the change works.
For a headless task, replace `MODEL_ID` with a tool-capable model from the live catalogue:
```bash
empryo --headless --model llmgateway/MODEL_ID \
"Fix the failing test and run it again"
```
Review the diff and test output, then check the request in your [dashboard](https://llmgateway.io/dashboard). Empryo tags gateway requests with `x-source: empryo` for attribution.
See the [Empryo documentation](https://empryo.com/docs) for additional desktop and terminal controls.
# GitHub Copilot App Integration
URL: https://llmgateway.io/guides/github-copilot
The [GitHub Copilot app](https://github.com/features/ai/github-app) supports custom model providers for agent sessions. Add LLM Gateway as an OpenAI-compatible endpoint and choose a model available to your workspace.
## Video walkthrough [#video-walkthrough]
## Install and sign in [#install-and-sign-in]
Install the app from GitHub's official download page and sign in with your GitHub account. Review the requested authorization before approving it.
GitHub account sign-in is required even when you supply your own model key. GitHub documents BYOK access without a Copilot subscription; managed accounts may be subject to organization policy. See its [BYOK setup guide](https://docs.github.com/en/copilot/how-tos/github-copilot-app/use-byok-models) for current requirements. The feature is in public preview.
## Add LLM Gateway [#add-llm-gateway]
1. Open app **Settings → Model providers**.
2. Click **Add provider** and choose an **OpenAI-compatible** endpoint.
3. Enter a display name, your gateway workspace key, and the base URL `https://api.llmgateway.io/v1`.
4. Save the provider and open the session's model picker.
Provider credentials are stored in the system credential store. Choose a model with tool calling and streaming support from the [live catalogue](https://llmgateway.io/models?features=tools), then test it in a small local project.
With a [DevPass](https://devpass.llmgateway.io) key, use a canonical model included in your plan. Upstream provider prefixes pin routing and are not supported on coding plans.
## Check reasoning settings [#check-reasoning-settings]
Model discovery may supply only model IDs. If the app does not retain your reasoning selection, edit the model under **Model providers** and configure its supported reasoning effort levels.
Use only levels supported by the selected model and mapping. The model's live catalogue page shows these capabilities; the models API exposes declared levels under `providers[].reasoning_efforts`.
## Verify a session [#verify-a-session]
Choose the connected provider and model explicitly. Ask the agent to inspect a small project, fix one failing test, and run the tests without changing them. Review permissions, the resulting diff, and the actual command output.
Check usage in the workspace that issued your gateway key. A configured provider or successful sign-in alone does not prove that an agent task completed.
## Troubleshooting [#troubleshooting]
**Sign-in expires:** restart the app's sign-in flow and complete the browser authorization before the device code expires.
**The provider or model is missing:** update the app, check the saved base URL and active key, and review managed-account policy.
**Reasoning selection resets:** configure supported levels on the provider's model entry.
**A request is rejected:** inspect the error, model capability, and workspace access. Do not assume every catalogue model supports the app's agent workflow.
For editor-based Copilot setup, see the separate [VS Code guide](https://docs.llmgateway.io/guides/vscode). BYOK works for Copilot Chat and agent mode in VS Code, but Copilot's inline code completions currently do not support BYOK or custom endpoints, including LLM Gateway.
# Hermes Agent Integration
URL: https://llmgateway.io/guides/hermes-agent
[Hermes Agent](https://github.com/NousResearch/hermes-agent) supports custom OpenAI-compatible endpoints. This walkthrough was verified with Hermes Agent 0.21.0.
## Video walkthrough [#video-walkthrough]
## Install [#install]
Follow the [official installation instructions](https://github.com/NousResearch/hermes-agent#installation), then open a new terminal and check:
```bash
hermes --version
```
## Configure the connection [#configure-the-connection]
Run `hermes setup` and choose **Custom OpenAI-compatible endpoint**. Enter:
| Setting | Value |
| ---------------- | ------------------------------------------------------------ |
| Base URL | `https://api.llmgateway.io/v1` |
| API key | A key from your [dashboard](https://llmgateway.io/dashboard) |
| Model | `deepseek-v4-flash` |
| Terminal backend | `local` for this walkthrough |
You can also configure the files directly. Merge the following into `~/.hermes/config.yaml`:
```yaml
model:
provider: custom
default: deepseek-v4-flash
base_url: https://api.llmgateway.io/v1
terminal:
backend: local
```
Store the credentials in `~/.hermes/.env`:
```dotenv
OPENAI_API_KEY=your_api_key
OPENAI_BASE_URL=https://api.llmgateway.io/v1
```
Keep the credentials file private. `HERMES_HOME` can point Hermes at a separate configuration directory.
## Start coding [#start-coding]
```bash
cd your-project
hermes chat
```
For a single task that exits when finished:
```bash
hermes chat --oneshot -q "Fix the failing test and run it again"
```
Use `--toolsets terminal,file` to limit this demo to shell and file tools. You can change the model for a session with `--model`.
## Verify the connection [#verify-the-connection]
Open a small project and ask the agent to read a file, make a change, and run its tests. Our recorded example fixes a TypeScript slugifier and passes all three tests with `deepseek-v4-flash` through LLM Gateway.
`Hello, LLM Gateway!` becomes `hello-llm-gateway`; repeated separators collapse into one hyphen; empty and punctuation-only inputs stay empty. The demo uses Node.js 24 to run `node --test slugify.test.ts` directly.
Review the diff and test output, then check the request in your [LLM Gateway dashboard](https://llmgateway.io/dashboard). Choose other compatible models from the [live catalogue](https://llmgateway.io/models?features=tools).
## Troubleshooting [#troubleshooting]
Run `hermes doctor` to inspect the configuration. Missing credentials for optional providers do not prevent your configured custom endpoint from working.
If requests use another provider, check both `model.provider: custom` and `model.base_url`. If TypeScript tests fail before running, check the Node.js version inside Hermes's terminal tool; it can differ from the version in your outer shell.
# Coding tool guides
URL: https://docs.llmgateway.io/guides
Every guide below connects a tool to LLM Gateway and ends with a verified coding task. The same steps work with a [DevPass](https://docs.llmgateway.io/devpass) key or a pay-as-you-go [LLM Gateway API key](https://docs.llmgateway.io/learn/api-keys). Requests sent through a supported tool show up per agent under [Coding Agents](https://docs.llmgateway.io/learn/coding-agents).
## Terminal coding agents [#terminal-coding-agents]
Connect Claude Code to LLM Gateway with environment variables and verify a
real file edit and test run.
Configure Codex CLI with an LLM Gateway provider, connect your API key, and
verify file edits and test execution.
Install DevPass Code, connect an LLM Gateway key, select a model, and verify
a real coding task.
Connect OpenCode to LLM Gateway and verify a coding task.
Connect Crush to LLM Gateway, discover models, and verify a coding task.
Configure Continue CLI with LLM Gateway, choose a tool-capable model, and
verify a coding task.
Connect Kimi Code to LLM Gateway, discover models, and verify a coding task.
Configure MiMo Code with LLM Gateway, select a compatible model, and verify
a coding task.
Configure Pi with LLM Gateway and verify a coding task.
Connect Oh My Pi to LLM Gateway or DevPass, discover models, and run a
coding task from your terminal.
Connect Hermes Agent to LLM Gateway using a custom endpoint, then verify
tool use, file edits, and tests.
Configure Reasonix with LLM Gateway and verify a coding task.
Connect Autohand Code’s built-in LLM Gateway provider with an API key,
choose a model, and verify a coding task.
Connect Anvil to LLM Gateway, choose DevPass or pay-as-you-go billing, and
configure its local coding engine.
Connect Empryo’s desktop app and terminal UI to LLM Gateway or DevPass,
choose a model, and run a coding task.
Configure OpenClaw with LLM Gateway, verify a local coding task, and
understand provider aliases and tool permissions.
## Editors and desktop apps [#editors-and-desktop-apps]
Connect Conductor's OpenCode agent to LLM Gateway, choose a model, and
verify your first request.
Configure Cursor’s custom OpenAI endpoint for LLM Gateway and understand
model compatibility, sign-in, and billing scope.
Install the official LLM Gateway extension and use your PAYG or DevPass key
in VS Code's native chat and agent mode.
Configure Cline's OpenAI-compatible provider, choose a model, and verify a
coding task through LLM Gateway.
Connect Kilo Code to LLM Gateway or DevPass, select a model, and review
agent edits and commands.
Configure the GitHub Copilot app with LLM Gateway, review account
prerequisites, and choose a compatible agent model.
Connect OpenCode Desktop to LLM Gateway or DevPass, open a project, and
verify an agent task.
## Automation and agent skills [#automation-and-agent-skills]
Connect n8n chat workflows to LLM Gateway with an OpenAI Chat Model, then
verify a real execution.
Packaged instructions and guidelines for AI coding agents
# Kilo Code Integration
URL: https://llmgateway.io/guides/kilo-code
[Kilo Code](https://kilo.ai/) is a coding agent for VS Code. Its provider settings include separate LLM Gateway and DevPass connections.
## Video walkthrough [#video-walkthrough]
## Install and choose permissions [#install-and-choose-permissions]
Install **Kilo Code** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=kilocode.kilo-code), then open its sidebar panel.
On first launch, choose how much autonomy to allow. **Review First** lets you inspect edits and shell commands before approving them. Start with a small project and a focused task.
## Connect your workspace [#connect-your-workspace]
1. Open Kilo's **Settings**, then **Providers**.
2. Click **Show more providers** and search for `llm`.
3. Choose **LLM Gateway** for pay-as-you-go usage or **DevPass (LLM Gateway)** for a coding plan.
4. Paste a key from the matching workspace dashboard and submit it.
The built-in provider supplies the endpoint; you do not need to enter a base URL.
## Select the model for this chat [#select-the-model-for-this-chat]
Click the model picker at the bottom of the chat and select a tool-capable model from your connected provider. Check the active model before sending a task: setting a default under **Settings > Models** does not necessarily change an already-open chat.
Use the [live model catalogue](https://llmgateway.io/models?features=tools) to check capabilities. For DevPass, choose a canonical model ID included in your plan; upstream provider prefixes pin routing and are not supported on coding plans.
## Verify a coding task [#verify-a-coding-task]
Ask Kilo to inspect a function, fix a failing test case, and run the existing tests without changing them. In **Review First** mode, inspect the proposed diff and use **Allow once** for the edit and test command when appropriate.
Review the command output and final diff. The key determines which workspace receives the requests; inspect usage in that workspace's dashboard.
## Troubleshooting [#troubleshooting]
**The provider is missing:** expand **Show more providers**, search for `llm`, and check that Kilo is current.
**The chat still uses another provider:** select the model in the chat itself, even if you already changed the default in Settings.
**Authentication fails:** verify that the key is active and matches the selected billing mode.
**A task pauses:** check whether Kilo is waiting for permission to edit a file or run a command.
# Kimi Code Integration
URL: https://llmgateway.io/guides/kimi-code
[Kimi Code](https://github.com/MoonshotAI/kimi-code) is a terminal coding agent. Its provider manager can discover LLM Gateway models and save the connection for future sessions.
## Video walkthrough [#video-walkthrough]
## Install [#install]
```bash
pnpm add -g @moonshot-ai/kimi-code
kimi --version
```
Use the current Kimi Code package. Additional installation options are in the [official documentation](https://github.com/MoonshotAI/kimi-code).
## Connect your account [#connect-your-account]
Create an API key in your LLM Gateway or DevPass dashboard, then launch `kimi` from your project. Review the workspace trust prompt before continuing.
1. Run `/provider`.
2. Select **Add New Platform**, then **Known third-party provider**.
3. Search for **LLM Gateway**.
4. Choose **LLM Gateway** for pay-as-you-go usage or **DevPass (LLM Gateway)** for a coding plan.
5. Enter your key when prompted, then choose a tool-capable model.
The provider registry supplies model metadata. Use the [live catalogue](https://llmgateway.io/models?features=tools) to check capabilities and account availability.
> **Using DevPass?** Choose a canonical model ID included in your plan. An upstream provider prefix pins routing and is not supported by coding plans. A local provider name in Kimi's model alias is separate from the model ID sent to the gateway.
## Verify a small task [#verify-a-small-task]
Ask Kimi to inspect a function, fix one failing test case, and run the existing tests without changing them. Review the diff and command output. Use `/model` to change the selected model for later work.
The key determines which workspace receives the requests. Check usage in that workspace's dashboard.
## Manual configuration [#manual-configuration]
Kimi stores its configuration in `~/.kimi-code/config.toml`. A custom OpenAI-compatible provider uses `type = "openai"` and `base_url = "https://api.llmgateway.io/v1"`. Keep the file private because it can contain your API key.
Model entries refer to the local provider name and specify the gateway model ID. Match their context limits, output limits, and capabilities to the selected model. Prefer the provider manager when you want the registry to populate those values.
## Troubleshooting [#troubleshooting]
**The provider list is unavailable:** check network access to the registry or use a custom provider configuration.
**Authentication fails:** check that the key is active and belongs to the intended workspace.
**A model is unavailable:** verify its exact ID and that your account or plan can use it.
**Tool calls fail:** choose a model with tool support and check the model metadata in your configuration.
# MiMo Code Integration
URL: https://llmgateway.io/guides/mimocode
[MiMo Code](https://mimo.xiaomi.com/mimocode) is a terminal coding agent. Its custom provider configuration can route Anthropic-compatible requests through LLM Gateway.
## Video walkthrough [#video-walkthrough]
## Install [#install]
Use the official installer, then check the version:
```bash
curl -fsSL https://mimo.xiaomi.com/install | bash
mimo --version
```
## Connect your workspace [#connect-your-workspace]
Create a key in your LLM Gateway or DevPass dashboard and export it in the terminal that will launch MiMo Code:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Merge this configuration into `~/.config/mimocode/mimocode.json` on macOS or Linux:
```json
{
"model": "anthropic/MODEL_ID",
"small_model": "anthropic/MODEL_ID",
"provider": {
"anthropic": {
"options": {
"apiKey": "{env:LLMGATEWAY_API_KEY}",
"baseURL": "https://api.llmgateway.io/v1"
},
"models": {
"MODEL_ID": {
"name": "Gateway coding model",
"id": "MODEL_ID",
"limit": {
"context": 200000,
"output": 8192
}
}
}
}
}
}
```
Replace every `MODEL_ID` with the exact gateway model ID. Choose a model with tool support from the [live catalogue](https://llmgateway.io/models?features=tools). Adjust the example context and output limits to values supported by that model.
Here `anthropic/` in the top-level selection identifies MiMo Code's local provider. The nested `id` is the model ID sent to LLM Gateway. The provider name selects the Anthropic API format; the base URL sends those requests to the gateway.
> **Using DevPass?** Keep the nested `id` canonical and choose a model included in your plan. Adding an upstream provider prefix to that ID pins routing and is not supported on coding plans.
## Verify a task [#verify-a-task]
```bash
cd your-project
mimo
```
Review the workspace trust prompt. Ask the agent to inspect a function, fix a failing test case, and run the existing tests without changing them. Approve file and command permissions as needed, then review the diff and test output.
Check usage in the workspace that issued your key.
## Troubleshooting [#troubleshooting]
**The request exceeds the model's output limit:** MiMo Code 0.1.15 forces a large output allowance for some model names, overriding `limit.output`. If your request is rejected, select a compatible model or update MiMo Code; changing the configured limit alone may not fix it.
**A tool schema is rejected:** MiMo Code's tool definitions may exceed what the selected model or upstream provider accepts. Try another tool-capable model and inspect the returned error.
**Authentication fails:** export the key before starting MiMo Code and confirm that the key is active.
**A model is unavailable:** check the nested model `id`, account access, and plan restrictions.
See the [MiMo Code provider reference](https://github.com/XiaomiMiMo/MiMo-Code/blob/main/packages/opencode/src/skill/builtin/.bundle/mimocode-docs/reference/providers.md) for other configuration options.
# n8n Integration
URL: https://llmgateway.io/guides/n8n
Connect an n8n **OpenAI Chat Model** to LLM Gateway to use an accessible model in an AI workflow. This walkthrough uses a chat trigger and an AI Agent.
## Video walkthrough [#video-walkthrough]
## Create a chat workflow [#create-a-chat-workflow]
1. Add **On chat message** as the first step. The canvas names it **When chat message received**.
2. Connect it to an **AI Agent** node.
3. Keep the agent's prompt source connected to the chat trigger. Its message expression is `{{ $json.chatInput }}`.
4. Click the plus under the agent's **Chat Model** connector and choose **OpenAI Chat Model**.
For a first connection test, leave memory and external tools disconnected.
## Add the gateway credential [#add-the-gateway-credential]
In **OpenAI Chat Model**, click **Connect to OpenAI** or create a new OpenAI credential from the credential selector.
| Field | Value |
| --------------- | ------------------------------ |
| API Key | Your LLM Gateway workspace key |
| Organization ID | Leave blank |
| Base URL | `https://api.llmgateway.io/v1` |
Save the credential and select it on the model node. The OpenAI credential selects the API format; the base URL directs requests to LLM Gateway.
Keep the key in n8n's credential store, rather than in a prompt or workflow expression.
## Configure the chat model [#configure-the-chat-model]
1. Turn **Use Responses API** off for this Chat Completions walkthrough.
2. Change the model selector to **By ID** and enter the exact ID from the [live catalogue](https://llmgateway.io/models).
3. For an agent that calls tools, choose a model with tool support. Set any output token limit within that model's supported range.
With a [DevPass](https://devpass.llmgateway.io) key, choose a canonical model included in your plan. Upstream provider prefixes pin routing and are not supported on coding plans.
## Run and inspect [#run-and-inspect]
Return to the canvas, click **Open chat**, and send a short prompt. Check the answer and the execution log for successful **AI Agent** and **OpenAI Chat Model** steps.
The gateway request belongs to the workspace that issued your key. Review its usage in that workspace's dashboard. n8n's execution details help identify which workflow step made the request.
Once the connection works, add memory or approved tools as needed. Review permissions before connecting actions that change external systems.
## Troubleshooting [#troubleshooting]
**Authentication fails:** check the saved credential, active key, and exact base URL, including `/v1`.
**The model is absent from the picker:** use **By ID** with an accessible model from the live catalogue.
**The model request fails:** confirm **Use Responses API** is off for this setup, then inspect the failing model step's error and token settings.
**The agent has no prompt:** connect the chat trigger and check the `chatInput` expression.
See the official [OpenAI Chat Model reference](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatopenai/) for node options.
# Oh My Pi Integration
URL: https://llmgateway.io/guides/oh-my-pi
[Oh My Pi](https://omp.sh) (`omp`) is a terminal coding agent with file editing, shell, language-server, and debugger tools. Connect it to LLM Gateway with a pay-as-you-go or DevPass API key.
## Video walkthrough [#video-walkthrough]
## Install Oh My Pi [#install-oh-my-pi]
On macOS or Linux:
```bash
curl -fsSL https://omp.sh/install | sh
omp --version
```
See the [installation guide](https://omp.sh/docs) for other platforms and package managers.
## Connect LLM Gateway or DevPass [#connect-llm-gateway-or-devpass]
Create an API key in your [LLM Gateway dashboard](https://llmgateway.io/dashboard) or [DevPass dashboard](https://devpass.llmgateway.io/dashboard). Export it in the terminal where you will run Oh My Pi:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Create or update `~/.omp/agent/models.yml`. Merge this entry into any existing `providers` mapping:
```yaml
providers:
llmgateway:
baseUrl: https://api.llmgateway.io/v1
api: openai-completions
apiKey: LLMGATEWAY_API_KEY
discovery:
type: openai-models-list
```
`apiKey` names the environment variable. Discovery loads the gateway's `/v1/models` catalogue, so you do not need to maintain a model list in the configuration. The same endpoint works with either kind of key; the key determines your workspace and billing.
## Select a model [#select-a-model]
Start Oh My Pi in your project:
```bash
cd your-project
omp
```
On first launch, finish the setup wizard; the configured provider appears in the model step.
Run `/model`, choose **llmgateway**, and select a text model with tool support from the [live catalogue](https://llmgateway.io/models?features=tools). With DevPass, choose a model supported by your plan.
To select a model when launching, replace `MODEL_ID` with its canonical catalogue ID:
```bash
omp --model llmgateway/MODEL_ID
```
Here, `llmgateway/` selects the custom provider inside Oh My Pi. It is not an upstream provider pin. Keep the remaining model ID canonical for DevPass routing.
## Verify the connection [#verify-the-connection]
Ask Oh My Pi to fix a small failing test in your project:
```text
Read the failing test, make the smallest fix, run the tests again,
and summarize the result. Do not edit the tests.
```
Review the diff and test output, then check usage in the dashboard for the workspace that issued your key.
For a one-shot task:
```bash
omp --model llmgateway/MODEL_ID -p "Explain this project without changing files"
```
## Troubleshooting [#troubleshooting]
* **Provider missing:** Check `~/.omp/agent/models.yml`, including YAML indentation, then restart `omp`.
* **Authentication failed:** Export `LLMGATEWAY_API_KEY` in the same shell before launching the agent.
* **Model rejected:** Select a tool-capable model available to your account. DevPass does not support upstream provider-pinned model IDs.
* **Discovery failed:** Check the base URL and use `openai-models-list` for the gateway's OpenAI-compatible catalogue.
See the [Oh My Pi model configuration reference](https://github.com/can1357/oh-my-pi/blob/main/docs/models.md) for additional settings.
# OpenClaw Integration
URL: https://llmgateway.io/guides/openclaw
[OpenClaw](https://openclaw.ai) runs agents locally and connects them to messaging channels. Configure LLM Gateway as a custom model provider, then verify a local task before connecting a channel.
## Video walkthrough [#video-walkthrough]
## Install [#install]
Use a Node.js version supported by the current [OpenClaw installation guide](https://docs.openclaw.ai/install). The recorded version requires Node 24.16+ or 26.1+.
```bash
pnpm add -g openclaw
openclaw --version
```
## Configure the provider [#configure-the-provider]
Export a key from the workspace you want to use:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Merge this provider into `~/.openclaw/openclaw.json`. Replace `MODEL_ID`, the display name, and the example limits with values for your chosen model from the [live catalogue](https://llmgateway.io/models?features=tools).
```json
{
"models": {
"mode": "merge",
"providers": {
"llmgateway": {
"baseUrl": "https://api.llmgateway.io/v1",
"apiKey": "${LLMGATEWAY_API_KEY}",
"api": "openai-completions",
"models": [
{
"id": "MODEL_ID",
"name": "Gateway coding model",
"contextWindow": 200000,
"maxTokens": 8192
}
]
}
}
},
"agents": {
"defaults": {
"model": { "primary": "llmgateway/MODEL_ID" }
}
}
}
```
`llmgateway/` in the primary model names OpenClaw's local provider entry. The nested `id` is the model identifier sent to LLM Gateway.
With a [DevPass](https://devpass.llmgateway.io) key, retain OpenClaw's local `llmgateway/` prefix and use a canonical model ID included in your plan. Do not add an upstream provider prefix inside the nested model ID.
## Verify a local agent task [#verify-a-local-agent-task]
From a small project you can review, run:
```bash
openclaw agent exec --config ~/.openclaw/openclaw.json --cwd . --code-mode direct "Read the failing test, fix the implementation, and run the tests. Do not change the tests or use subagents."
```
`agent exec` runs one agent turn without a running OpenClaw gateway. It can execute commands and change files in the selected workspace, so use a controlled project. A successful exit alone does not prove the task was completed: inspect the diff and run the tests independently.
For a focused coding workflow, restrict the tool policy in your configuration:
```json
{
"tools": {
"profile": "coding",
"allow": ["read", "write", "edit", "exec", "process"]
}
}
```
Merge this with your configuration rather than replacing the provider settings. Review the [OpenClaw agent command reference](https://docs.openclaw.ai/cli/agent) for execution policy and isolation options. `--auth-env-only` cannot be combined with `--config`.
## Connect channels after verification [#connect-channels-after-verification]
Follow OpenClaw's channel-specific setup for the messaging service you use. Review who can reach the agent and which tools it may call before enabling incoming messages.
Requests use the workspace associated with your gateway key. Review model usage and errors in that workspace's dashboard.
## Troubleshooting [#troubleshooting]
**Startup rejects Node.js:** update to a supported runtime and check the version in the shell launching OpenClaw.
**The model is unavailable:** check the primary provider alias, nested model ID, account access, and catalogue limits.
**The agent only describes a plan:** verify that tools are enabled and the selected model actually returns tool calls. Try a compatible tool-capable model and inspect the resulting file changes; a text-only answer is not evidence of execution.
**A key is missing:** export the environment variable before launching OpenClaw, including when starting it through a service manager.
# OpenCode Desktop Integration
URL: https://llmgateway.io/guides/opencode-desktop
[OpenCode Desktop](https://opencode.ai/download) is a desktop interface for agent sessions in local projects. Its built-in provider picker includes LLM Gateway and DevPass.
## Video walkthrough [#video-walkthrough]
## Install and connect [#install-and-connect]
Download OpenCode Desktop from the [official site](https://opencode.ai/download).
1. Open the provider connection control on the start screen.
2. Search for **LLM Gateway**.
3. Choose **LLM Gateway** for pay-as-you-go usage or **DevPass (LLM Gateway)** for a coding plan.
4. Enter an API key from the matching dashboard and click **Continue**.
The built-in provider supplies the endpoint. You do not need a manual base URL for the hosted gateway.
## Open a project [#open-a-project]
Use the project picker below the composer and select **Add project**. Choose the local folder where you want the agent to work.
Check the project name before submitting a task. Start with a small repository whose changes you can review.
## Choose a model [#choose-a-model]
Use the model picker in the composer to select a tool-capable model from your connected provider. The [live catalogue](https://llmgateway.io/models?features=tools) lists current capabilities and availability.
For DevPass, choose a canonical model included in your plan. Upstream provider prefixes pin routing and are not supported on coding plans.
## Verify a small change [#verify-a-small-change]
Ask OpenCode to inspect a function, fix a failing test case, and run the existing tests without changing them. Review permissions before approving requested actions.
Expand the tool activity to inspect edits and shell output. Check the test result and resulting diff before keeping the change. Requests appear in the workspace that issued your API key.
## Troubleshooting [#troubleshooting]
**The provider is missing:** update OpenCode Desktop and search the complete provider list.
**Authentication fails:** check that your key is active and matches the selected billing mode.
**The agent works in the wrong folder:** use the project picker before starting a session.
**A model is unavailable:** confirm its exact ID, tool support, and availability to your account or plan.
# OpenCode Integration
URL: https://llmgateway.io/guides/opencode
[OpenCode](https://opencode.ai) is an open-source coding agent for your terminal, desktop, and editor. LLM Gateway is a built-in provider, with usage tracked in your gateway dashboard.
This walkthrough was verified with OpenCode 1.18.27, including a file edit and a successful test run.
## Video walkthrough [#video-walkthrough]
## Install [#install]
Use the [OpenCode download page](https://opencode.ai/download) for your platform, or install the CLI:
```bash
pnpm add -g opencode-ai
opencode --version
```
## Connect your API key [#connect-your-api-key]
Start OpenCode in your project:
```bash
opencode
```
Run `/connect`, search for **LLM Gateway**, and enter a key from your [LLM Gateway dashboard](https://llmgateway.io/dashboard). OpenCode saves credentials for later sessions.
Open `/models` to choose a model. Browse the [live catalogue](https://llmgateway.io/models) for current capabilities and pricing.
> **Using DevPass?** Select a canonical model ID without an upstream provider prefix. Provider-pinned routing is not available on coding plans.
## Configure with an environment variable [#configure-with-an-environment-variable]
For scripts or a project-specific configuration, set your key:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Add an `opencode.json` in the project root:
```json
{
"provider": {
"llmgateway": {
"options": {
"apiKey": "{env:LLMGATEWAY_API_KEY}"
}
}
},
"model": "llmgateway/deepseek-v4-flash"
}
```
`llmgateway` is OpenCode's provider ID. The rest of the `model` string is the gateway model ID. The example uses the model tested in this walkthrough; replace it with one from the live catalogue.
The provider already defines the gateway endpoint, so this configuration only supplies the key and model. For a custom endpoint override, use `provider.llmgateway.options.baseURL` with `https://api.llmgateway.io/v1`.
## Verify a coding task [#verify-a-coding-task]
Ask OpenCode to fix a specific file and run its tests:
```text
Fix slugify.ts so all tests pass. Read the files, make the smallest fix,
run node --test slugify.test.ts, and summarize. Do not edit the tests.
```
Review the diff, approve commands as needed, and check the test output. Then confirm the request's model, tokens, and cost in your [dashboard](https://llmgateway.io/dashboard).
You can also run a one-shot task:
```bash
opencode run --model llmgateway/deepseek-v4-flash "Explain this project"
```
## Switching models [#switching-models]
Use `/models` in the terminal, or list the provider's available IDs:
```bash
opencode models llmgateway
```
Copy an exact ID from that output into the `model` field or the `--model` option. Canonical IDs let the gateway choose the upstream provider; provider-prefixed IDs select a specific deployment when your account supports pinned routing.
## Troubleshooting [#troubleshooting]
### Provider not found [#provider-not-found]
Check `opencode --version` and run `opencode models llmgateway`. The verified version uses `llmgateway`; older instructions referring to `llmgateway-providers` may not match your installed release.
### Authentication error [#authentication-error]
Check that the key is active and available in the shell that launches OpenCode. The environment reference uses OpenCode's `{env:VARIABLE_NAME}` syntax.
### Model missing [#model-missing]
Refresh the model picker or restart OpenCode. Copy the exact ID from `opencode models llmgateway` and compare it with the [gateway catalogue](https://llmgateway.io/models).
See [OpenCode's provider documentation](https://opencode.ai/docs/providers/#llm-gateway) for additional configuration options.
# Pi Integration
URL: https://llmgateway.io/guides/pi
[Pi](https://pi.dev) is a terminal coding agent with tools for reading files, editing code, and running commands. Add LLM Gateway as a custom provider to choose from the [live model catalogue](https://llmgateway.io/models) and track usage in one dashboard.
This setup was verified with Pi 0.85.1, including file edits and a successful test run.
## Video walkthrough [#video-walkthrough]
## Install Pi [#install-pi]
Install the current package:
```bash
pnpm add -g @earendil-works/pi-coding-agent
pi --version
```
The older `@mariozechner/pi-coding-agent` package is deprecated. See the [Pi website](https://pi.dev) for installation options.
## Configure the provider [#configure-the-provider]
Create an API key in your [LLM Gateway dashboard](https://llmgateway.io/dashboard), then set it in the shell where you will run Pi:
```bash
export LLMGATEWAY_API_KEY="your_api_key"
```
Add this provider to `~/.pi/agent/models.json`. Merge it with any existing providers:
```json
{
"providers": {
"llmgateway": {
"baseUrl": "https://api.llmgateway.io/v1",
"api": "openai-completions",
"apiKey": "$LLMGATEWAY_API_KEY",
"headers": { "x-source": "pi" },
"models": [
{
"id": "deepseek-v4-flash",
"reasoning": true,
"contextWindow": 1050000,
"maxTokens": 4096
}
]
}
}
}
```
The model is a working example. Choose a text model with tool support from the [models page](https://llmgateway.io/models), and use its exact ID. Match `reasoning` and `contextWindow` to the selected model. This example caps the response at 4,096 tokens with `maxTokens`.
**Keep the `$` in `"$LLMGATEWAY_API_KEY"`.** Current Pi interpolates environment variables only with `$NAME` or `${NAME}`. A plain `"LLMGATEWAY_API_KEY"` is sent as the literal key and causes an authentication error.
> **Using DevPass?** Use a canonical model ID without an upstream provider prefix. Coding plans let the gateway select the serving provider.
## Select the model [#select-the-model]
Start Pi from your project directory and open the model picker:
```bash
pi
```
Type `/model`, then choose the LLM Gateway model. Pi reloads `models.json` when you open the picker, so configuration changes do not require a restart.
You can also select the provider and model when launching:
```bash
pi --provider llmgateway --model deepseek-v4-flash
```
## Verify the connection [#verify-the-connection]
Give Pi a small task with a clear check:
```text
Fix slugify.ts so all tests pass. Read the files, make the smallest fix,
run node --test slugify.test.ts, and summarize. Do not edit the tests.
```
Inspect the resulting diff and test output. Requests appear in your [LLM Gateway dashboard](https://llmgateway.io/dashboard), with the model, token usage, and cost.
For a one-shot command:
```bash
pi --provider llmgateway --model deepseek-v4-flash -p "Explain this project"
```
## Troubleshooting [#troubleshooting]
* **Authentication error:** Check that the shell variable is set, the key is active, and `apiKey` includes the `$` prefix.
* **Model missing:** Copy its exact ID from the live catalogue, then reopen `/model`.
* **Wrong endpoint:** Use `https://api.llmgateway.io/v1` with `api: "openai-completions"`.
* **Unsupported reasoning setting:** Match the model's reasoning support and supported effort levels.
See Pi's [custom provider documentation](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md) for advanced configuration.
# Reasonix Integration
URL: https://llmgateway.io/guides/reasonix
[Reasonix](https://github.com/esengine/DeepSeek-Reasonix) is an open-source coding agent with a terminal interface, desktop app, and editor integration. Its current release supports OpenAI-compatible endpoints, so you can connect it directly to LLM Gateway.
This walkthrough was verified with Reasonix 1.38.1: the agent read a TypeScript project, edited a function, and ran its tests through the gateway.
## Video walkthrough [#video-walkthrough]
## Install [#install]
Install the current native CLI:
```bash
pnpm add -g reasonix
reasonix --version
```
See the [official installation instructions](https://github.com/esengine/DeepSeek-Reasonix#install) for other platforms and installation methods. These instructions apply to Reasonix 1.x; the older 0.x release uses a different configuration format.
## Connect to LLM Gateway [#connect-to-llm-gateway]
Run the provider setup:
```bash
reasonix setup
```
Add an **OpenAI-compatible** provider with these values:
| Setting | Value |
| ------------- | ----------------------------------------------------------------------------------- |
| Provider name | `llmgateway` |
| Base URL | `https://api.llmgateway.io/v1` |
| API key | Your LLM Gateway API key |
| Model | A text model with tool support from the [models page](https://llmgateway.io/models) |
Choose **Save and exit**, then select this provider as your default.
Reasonix stores provider definitions in `~/.reasonix/config.toml` on macOS and Linux, or `%AppData%\reasonix\config.toml` on Windows. A minimal configuration looks like this:
```toml
default_model = "llmgateway"
[[providers]]
name = "llmgateway"
kind = "openai"
base_url = "https://api.llmgateway.io/v1"
model = "deepseek-v4-flash"
api_key_env = "LLMGATEWAY_API_KEY"
headers = { x-source = "reasonix" }
```
The model above is the example used for this walkthrough. Copy your preferred model ID from the live catalogue.
**Save the key through `reasonix setup`.** The `api_key_env` field names the entry in Reasonix's own `.env` file. Current Reasonix reads provider secrets from that file inside its configuration directory; exporting a shell variable alone does not configure the key.
> **Using DevPass?** Use a canonical model ID without an upstream provider prefix. The gateway selects the serving provider for coding plans.
## Try a coding task [#try-a-coding-task]
Start Reasonix from your project directory:
```bash
cd your-project
reasonix
```
Ask it to fix a specific problem and verify the result:
```text
Fix slugify.ts so all tests pass. Read the files, make the smallest fix,
run node --test slugify.test.ts, and summarize. Do not edit the tests.
```
Reasonix can read files, propose edits, and execute tests. Review any permission prompts before allowing commands. For a one-shot task, use `reasonix run "your task"`.
Check the request's model, tokens, and cost in your [LLM Gateway dashboard](https://llmgateway.io/dashboard).
## Troubleshooting [#troubleshooting]
### Missing key or authentication error [#missing-key-or-authentication-error]
Open `reasonix setup` and save your LLM Gateway key for the configured provider. Check that `api_key_env` matches the entry in Reasonix's own `.env` file. Keep that file out of your project repository.
### Model not found [#model-not-found]
Copy the exact ID from the [models page](https://llmgateway.io/models). In Reasonix, `--model llmgateway` selects the configured provider; the provider's `model` field selects the gateway model.
### Requests go to another provider [#requests-go-to-another-provider]
Set `default_model = "llmgateway"`, or launch with `reasonix --model llmgateway`. Check for a project-level `reasonix.toml`, which can override your user configuration.
### Connection errors [#connection-errors]
Use `kind = "openai"` and include `/v1` in the base URL. For configuration and diagnostic commands, see the [Reasonix guide](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md).
# VS Code Integration
URL: https://docs.llmgateway.io/guides/vscode
The official [LLM Gateway extension](https://marketplace.visualstudio.com/items?itemName=llmgateway.llmgateway-vscode) adds LLM Gateway to the Copilot Chat model picker in VS Code. Connect your key once, then choose from the chat models available to your account.
Copilot Chat and agent mode support BYOK models. Copilot's inline code
completions (the gray autocomplete suggestions accepted with Tab) currently do
not support BYOK or custom endpoints, including LLM Gateway.
Verified with VS Code **1.135.0** and the LLM Gateway extension **0.1.0**. The video demonstrates model configuration and a real response without GitHub sign-in.
## Video walkthrough [#video-walkthrough]
## Prerequisites [#prerequisites]
* VS Code 1.134.0 or later with Copilot Chat available.
* An LLM Gateway API key from your [dashboard](https://llmgateway.io/dashboard), or a [DevPass](https://devpass.llmgateway.io) plan key.
## Install and connect [#install-and-connect]
1. Install **LLM Gateway**, published by **llmgateway**, from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=llmgateway.llmgateway-vscode).
2. Open the chat view, click the model picker, and choose **Manage Models…**.
3. Select **LLM Gateway** and enter your API key when prompted.
4. If VS Code prompts you to configure BYOK utility models, set **Chat: Byok Utility Model Default** to **Main Agent Model**.
5. Select an LLM Gateway model from the picker and send a message.
You can also install from the terminal:
```bash
code --install-extension llmgateway.llmgateway-vscode
```
The gateway URL is already configured. Your key is stored in VS Code's secret storage, never in settings files. Run **LLM Gateway: Set API Key** from the Command Palette to replace it, or **LLM Gateway: Clear API Key** to remove it.
## Choose a model [#choose-a-model]
The extension fetches models using your key and lists active chat models with streaming support. Your organization's model access rules and DevPass plan restrictions apply. Browse the [models page](https://llmgateway.io/models) for current capabilities and pricing.
For agent mode, select a model with tool calling. Image attachments require a model with image input support. Responses stream into the chat view.
Both PAYG and DevPass keys use the same setup. PAYG requests use your gateway billing configuration; DevPass requests follow your plan's model access and usage limits. The extension sends canonical model IDs, so no provider prefix is needed for DevPass.
BYOK chat works without GitHub sign-in or a Copilot plan. Utility tasks can use the selected model; semantic search and inline completions still have separate authentication requirements. See the [VS Code model documentation](https://code.visualstudio.com/docs/agent-customization/language-models).
## Settings [#settings]
Search for **LLM Gateway** in VS Code Settings to configure:
| Setting | Default | Purpose |
| ---------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `llmgateway.baseUrl` | `https://api.llmgateway.io/v1` | Change the endpoint for a self-hosted gateway. Include `/v1`. |
| `llmgateway.models` | `[]` | Limit the picker to specific model IDs. An empty list shows all eligible chat models your key can access. |
| `llmgateway.reasoningEffort` | `default` | Override reasoning effort for reasoning-capable models. Choose a level supported by the selected model. |
Run **LLM Gateway: Refresh Models** to reload the model list, or **LLM Gateway: Open Dashboard** to review usage.
## Troubleshooting [#troubleshooting]
### Models do not appear [#models-do-not-appear]
Check that the extension is enabled and VS Code meets the minimum version. Run **LLM Gateway: Set API Key**, then **LLM Gateway: Refresh Models**. If you configured `llmgateway.models`, clear it to `[]` to see all eligible models for your key.
### Authentication fails [#authentication-fails]
A `401` response clears the stored key. Run **LLM Gateway: Set API Key** with an active key from your dashboard.
### Credits or plan limits [#credits-or-plan-limits]
Read the gateway's error message. PAYG accounts may need credits; DevPass accounts may have reached a plan limit. Check the relevant dashboard before retrying.
For connection errors, open **View → Output** and select **LLM Gateway**. If you use a self-hosted gateway, check `llmgateway.baseUrl`.
Source code and issue reporting are available in the [extension repository](https://github.com/theopenco/llmgateway-vscode).
# Lounge connectors
URL: https://docs.llmgateway.io/features/lounge-connectors
Open **Connectors** in the Lounge to connect an account through OAuth. Connections are private to your account, and tool calls require your approval before they run.
On iOS, open **Connectors** from the home screen. Sign-in opens in the system browser and returns to the app. You can search connections, pause them, reconnect, or disconnect from this screen.
In an iOS conversation, review each proposed tool and its arguments, then choose **Approve** or **Decline**. The response continues once all pending requests are answered. Saved conversations retain pending approvals and completed results across restarts; temporary conversations keep them only for the current session.
In an iOS model comparison, connected apps are available to **Model 1**. Approve or decline its pending requests before sending another message to all models. Other models keep separate conversations and can be retried without running connector actions. Pending requests and results survive restarts.
If a tool's result cannot be confirmed, check the connected app before asking to run it again. **Continue response** lets the model respond to that uncertainty without repeating the tool call.
A connector stays **Not configured** until the deployment has both its OAuth client ID and secret. Missing credentials also disable existing connections: you cannot enable, reconnect, or use their tools until configuration is restored. You can still disconnect an account.
Pausing a configured connection retains its credentials and stops tool access. Disconnecting deletes its stored credentials and pending authorizations. Tool results used in a chat are sent to the selected model and saved with that conversation.
Authorization must finish in the same session that started it and expires after ten minutes. If you sign out or the request expires during consent, start connecting again. Cancelling reconnection keeps the existing connection.
## Client integrations [#client-integrations]
Authenticated clients can stream chat and signed tool proposals from `POST /lounge/chat`, using `x-llmgateway-key` to select the billing key. The endpoint accepts AI SDK UI messages and connector IDs. It does not execute tools or save conversations.
After the user approves a proposal, execute it through `POST /connectors/{connectorId}/tools/{toolName}` and record its result before continuing. Send completed, failed, or denied tool parts with the next chat request. Pending approvals must be resolved first; never automatically retry a tool whose outcome is unknown.
For deployment variables and callback setup, see the [connector setup guide](https://github.com/theopenco/llmgateway/blob/main/docs/lounge-connectors.md).
# The Lounge for iOS
URL: https://docs.llmgateway.io/features/lounge-ios
The Lounge opens directly to a new conversation. Choose a model above the chat,
type a message, and tap the send arrow. Use the plus button to attach a file.
Available models and capabilities are listed in the [model directory](https://llmgateway.io/models).
## Conversations and tools [#conversations-and-tools]
Open the sidebar in the top-left corner to return to a recent conversation,
search your conversation history, or open projects, images, videos, and voice
calls. **All tools** includes model comparison, Canvas, speech generation,
live transcription, skills, connectors, and Sandbox Escape.
The new-conversation button starts a fresh chat. Open **Conversation options**
beside the model selector for chat settings, temporary conversations, and saved
conversation actions such as renaming, pinning, sharing, and forking.
In Video Studio, tap a saved prompt to expand its video. **Preparing playback…**
means generation has finished and the playback link is being prepared; the
player appears automatically when ready. If playback fails, **Refresh video**
reloads the player and renews the playback link. If the link remains unavailable
for a minute, automatic retries stop and the app prompts you to refresh.
## Dictation and voice [#dictation-and-voice]
Tap the microphone in the composer to dictate a message. Allow microphone access
when iOS asks, speak, then tap **Done** to finish transcription. Review or edit
the text in the composer before sending it. Cancel discards the dictation.
Dictation uses an available transcription model and counts toward normal model
usage.
Tap the waveform button in an empty composer to open voice calls. Choose a
compatible model and voice, then start the call. Live transcription is also
available separately under **All tools**.
If microphone access is denied, open the app's microphone settings from the
recording screen. Recording stops when the app goes into the background or you
leave the recording screen. You can switch from video playback to dictation,
live transcription, or voice calls.
## Appearance [#appearance]
The interface uses a neutral black-and-white palette in both light and dark
mode. Open **Your profile → Appearance** to follow the system setting or choose
a theme. This choice stays on the device after signing out.
# Coding Agents
URL: https://docs.llmgateway.io/learn/coding-agents
The Coding Agents section of the [DevPass dashboard](https://devpass.llmgateway.io/dashboard)
breaks your DevPass usage down by the tool that produced it, so you can tell at
a glance whether your allowance is going to Claude Code, Cursor, OpenCode, or
something else.
Agents are recognised from the `x-source` header their requests send. Supported
tools include DevPass Code, Claude Code, OpenCode, Cursor, Autohand Code,
Anvil, Empryo, SoulForge, Cline, Codex CLI, GitHub Copilot, n8n and OpenClaw —
each
one has a setup guide under [Guides](https://docs.llmgateway.io/guides).
The overview lists the **last 7 days**. Open an agent to pick a wider window.
If nothing shows up yet, point your tool at DevPass with the two environment
variables from the dashboard's Quick start card and run it once.
## Agent Cards [#agent-cards]
| Field | Description |
| --------------- | ------------------------------------------------ |
| **Name** | The coding tool, matched from its request source |
| **Total cost** | Spend attributed to this agent |
| **Requests** | Number of API requests |
| **Tokens** | Prompt and completion tokens combined |
| **Last active** | When the agent last sent a request |
Click a card to open the agent's detail page.
## Agent Detail [#agent-detail]
The header repeats the agent's totals for the selected period and holds the
two controls for the page:
* **Time range** — 1h, 4h, 24h, 7d or 30d (7d by default), stored in the URL so
the view can be shared.
* **Export CSV** — downloads every request in the period, not just the rows
currently loaded.
Below it are three blocks:
**Model usage chart** — spend over time, split by model.
**Model usage table** — one row per model with Provider, Requests, Tokens,
Cost, Cache cost, and a share-of-tokens bar. Every column header sorts.
**Requests** — the individual calls, newest first, each showing the model used,
cache status, token count and cost. More rows load as you scroll, or with
**Load more**.
## Related pages [#related-pages]
* [**Model Categories & Fair Use**](https://docs.llmgateway.io/learn/model-categories) — which models
count against the premium allowance
* [**Agents Monitoring**](https://docs.llmgateway.io/learn/agents) — the same breakdown for a regular
gateway project, with sessions and BYOK filtering
# Model Categories & Fair Use
URL: https://docs.llmgateway.io/learn/model-categories
DevPass subscription prices stay the same, but included usage decreases. Daily
and premium weekly caps and Reset Pass benefits change on October 15; existing
monthly allowances change at the first renewal on or after that date. New
subscriptions from that date start with the new allowance. Review
the [dated plan-change
notice](https://devpass.llmgateway.io/legal/terms#october-2026-plan-changes)
before subscribing.
Every model in the gateway is sorted into a category. Categories power dashboard filtering, analytics, and — for DevPass coding plans — the fair-use limits that keep flagship models available to everyone.
## Categories [#categories]
| Category | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| **Premium** | High-cost frontier / flagship models — priced at **$15+ per million output tokens** or **$5+ per million input tokens** |
| **Standard** | Every other model — the broad catalog of fast, cost-effective everyday models |
The classification is computed automatically from catalogue prices — there is no hand-picked list. To see exactly which models are premium right now, check the live [**Premium Models**](https://llmgateway.io/models/premium) page, or browse the full catalog on the [**Supported Models**](https://llmgateway.io/models) page and filter by use case, capabilities, provider, price, and context size. On the [DevPass models directory](https://devpass.llmgateway.io/models), you can additionally filter by pricing tier, since the premium/standard distinction only affects DevPass fair-use limits.
## Fair-use caps on premium models (DevPass only) [#fair-use-caps-on-premium-models-devpass-only]
Fair-use caps apply **only to DevPass** — the fixed-price monthly plans for
coding tools (Lite, Pro, Max). They do **not** apply to the LLM Gateway API or
pay-as-you-go credits: when you call the API directly, premium models are
limited only by your credit balance, with no weekly cap.
Premium models are the most expensive to run, so DevPass plans apply a **weekly fair-use cap** on premium usage. The cap works on a fixed 7-day window: the window opens with your first premium-model request, all premium usage during those 7 days counts against the cap, and the full allowance resets at once when the window ends. It sits on top of the plan's normal monthly credit allowance. The cap scales with each plan: it's a share of that plan's total monthly credits, so higher tiers get a larger weekly premium allowance.
| DevPass plan | Premium fair-use cap (per week) |
| ------------ | ------------------------------- |
| **Lite** | 12% of monthly credits |
| **Pro** | 15% of monthly credits |
| **Max** | 18% of monthly credits |
Within DevPass, the weekly cap applies only to **premium** models. Standard
models are limited only by the plan's credit balance, not by the fair-use
window.
Once a DevPass plan reaches its weekly premium cap, premium requests are paused until the current 7-day window ends — usage does not gradually free up over time. The reset countdown runs from when the window opened, not from when you hit the cap, so the full allowance can return anywhere from moments to 7 days after you reach the limit. Standard models keep working normally the whole time. Upgrading the DevPass plan raises the weekly cap.
The fair-use window is a limiter on the plan allowance, not on your own money: with [pay-as-you-go overflow](https://docs.llmgateway.io/learn/payg-overflow) enabled and a positive credits balance, premium requests past the weekly cap keep flowing and bill that balance at provider rates — mid-cycle with monthly allowance remaining, or after the monthly pool is fully used alike. Standard models keep drawing from the plan allowance as usual.
## Reset Passes [#reset-passes]
You don't have to wait out the window: redeeming a [**Reset Pass**](https://docs.llmgateway.io/learn/reset-passes) instantly restores the full weekly premium allowance and starts a fresh 7-day window. A pass removes the weekly limit only — it grants no extra credits, so premium usage after a reset still draws from the plan's monthly allowance. Pro includes 1 pass per billing cycle and Max includes 2; extra passes are a one-time purchase from the DevPass dashboard. See the [Reset Passes page](https://docs.llmgateway.io/learn/reset-passes) for how redemption, included passes, and billing work.
## Provider routing on DevPass [#provider-routing-on-devpass]
DevPass plans always use the gateway's [smart routing](https://docs.llmgateway.io/features/routing): request a model by its plain id (e.g. `claude-sonnet-5`) and the gateway picks the best provider in real time based on uptime, throughput, price, and prompt-cache support. [Sticky session routing](https://docs.llmgateway.io/features/routing#sticky-session-routing) still keeps each coding session on one provider automatically, so upstream prompt caches stay warm across a conversation.
Both `auto` and `price` routing account for cached input costs using your project's recent model usage once enough history exists, and assume **90% cached input and a 2% output:input ratio** before then, including when choosing a provider for a short opening session prompt. This helps sessions that repeatedly reuse context choose providers with lower expected token costs. See [how routing learns your token mix](https://docs.llmgateway.io/features/routing#smart-routing-algorithm) for the sample requirements, defaults, and override behavior.
**Provider pinning is not available on DevPass.** Provider-prefixed model ids
such as `openai/gpt-4o` — which pin a request to one specific provider on
pay-as-you-go — are rejected with a `403` on DevPass coding plans, as is
custom provider routing. If you need to target an exact provider or region,
use [pay-as-you-go credits](https://llmgateway.io) with the LLM Gateway API,
where [provider-specific routing](https://docs.llmgateway.io/features/routing#provider-specific-routing)
is fully supported.
# Pay-As-You-Go Overflow
URL: https://docs.llmgateway.io/learn/payg-overflow
DevPass subscription prices stay the same, but included usage decreases. Daily
and premium weekly caps and Reset Pass benefits change on October 15; existing
monthly allowances change at the first renewal on or after that date. New
subscriptions from that date start with the new allowance. Review
the [dated plan-change
notice](https://devpass.llmgateway.io/legal/terms#october-2026-plan-changes)
before subscribing.
By default a DevPass plan is a hard cap: once the monthly allowance is fully used, requests are rejected until renewal. **Pay-as-you-go overflow** is an opt-in on the DevPass **Usage** page that keeps requests flowing past the allowance — extra usage bills a credits balance at provider rates, with the same API key and the same coding agents. The Usage page also carries the full allowance meters, the [weekly premium allowance](https://docs.llmgateway.io/learn/model-categories), [Reset Passes](https://docs.llmgateway.io/learn/reset-passes), and the model usage chart.
Overflow is **off by default** — until you enable it, nothing ever charges
past your subscription price. It never touches your plan allowance either:
credits cover only usage the plan itself wouldn't serve — spend past the
monthly pool, and premium usage past the weekly cap.
## When overflow applies [#when-overflow-applies]
| Situation | Overflow off (default) | Overflow on |
| ------------------------------------------- | ---------------------------- | ---------------------------------- |
| Within the monthly allowance | Plan credits | Plan credits |
| Monthly allowance exhausted | Requests rejected with `402` | Requests bill your credits balance |
| Weekly premium cap hit, allowance remaining | Reset Pass or wait | Premium models bill your balance |
| Weekly premium cap hit, allowance exhausted | Requests rejected with `402` | Premium models bill your balance |
| Credits balance empty | — | Requests rejected with `402` |
The weekly premium fair-use cap limits what the **plan allowance** pays for, not what your own balance can: with overflow on and a positive balance, premium requests past the cap keep flowing and bill your credits at provider rates, while standard models keep drawing from the monthly allowance as usual. A [Reset Pass](https://docs.llmgateway.io/learn/reset-passes) is still the way to keep that premium spend inside your already-paid plan allowance instead of on the balance. Once the monthly pool itself is gone, overflow covers everything.
If the allowance runs out with overflow off (or with an empty balance), the gateway's `402` says what to do:
```json
{
"error": {
"message": "Dev Plan credit limit reached. Upgrade your plan or wait for renewal on 8/24/2026. Or enable pay-as-you-go overflow in your DevPass dashboard to keep going past your allowance.",
"type": "invalid_request_error",
"code": "billing_error"
}
}
```
## Topping up [#topping-up]
Enable overflow on the Usage page, pick a preset ($10, $25, $50, $100) or a custom amount between **$10 and $5,000**, and **Top up** — the charge goes to the card already backing your DevPass subscription, with no checkout redirect. Usage itself is metered at provider rates with no markup on tokens; a 5% platform fee applies when you buy credits (plus 1.5% for international cards). Each top-up emails a PDF invoice and appears in your billing history.
Top-ups are idempotent end to end: a double-click or a retried request reuses the same charge instead of creating a second one.
## Auto-reload [#auto-reload]
Turn on **Auto-reload** and the balance refills itself: when it falls below your threshold ($5–$1,000), your reload amount ($10–$5,000) is charged to the saved card automatically.
Guardrails worth knowing:
* **Disabling overflow always disables auto-reload** — you can never buy credits you cannot spend.
* Failed auto-reload charges back off exponentially (1h doubling up to 24h), and auto-reload switches itself off after 7 days of continuous payment failures.
Overflow is available on **every DevPass tier**. If you find yourself covering
a lot of usage from the balance every cycle, a tier upgrade is usually the
better deal: plan allowances turn every subscription dollar into $3 of usage,
while overflow bills at cost. Compare on the [DevPass pricing
page](https://devpass.llmgateway.io/pricing).
# Reset Passes
URL: https://docs.llmgateway.io/learn/reset-passes
DevPass subscription prices stay the same, but included usage decreases. Daily
and premium weekly caps and Reset Pass benefits change on October 15; existing
monthly allowances change at the first renewal on or after that date. New
subscriptions from that date start with the new allowance. Review
the [dated plan-change
notice](https://devpass.llmgateway.io/legal/terms#october-2026-plan-changes)
before subscribing.
DevPass plans include a [weekly fair-use allowance](https://docs.llmgateway.io/learn/model-categories) for premium frontier models. A **Reset Pass** restores that full allowance the moment you redeem it — no waiting for the rolling 7-day window — and a fresh window starts with your next premium request. The pass card lives on the DevPass dashboard directly under the premium allowance meter, styled as a stamped visa extension: each pass you hold appears as a stamp.
A Reset Pass removes the weekly limit — it does **not** add credits. Premium
usage after a reset draws from your plan's monthly credit allowance exactly as
before; the pass just lets you keep using premium models now instead of
waiting for the window. With [pay-as-you-go overflow](https://docs.llmgateway.io/learn/payg-overflow)
enabled, premium usage past the cap bills your credits balance at provider
rates instead — a pass is what brings that spend back inside the plan
allowance you already paid for. And once the monthly allowance itself is
exhausted, overflow is what keeps requests flowing, not a pass.
Reset Passes exist only on **DevPass** (Lite, Pro, Max). Pay-as-you-go API
usage has no weekly premium cap, so there is nothing to reset.
## Included and purchased passes [#included-and-purchased-passes]
| Plan | Included per billing cycle | Extra passes |
| -------- | -------------------------- | ------------ |
| **Lite** | — | $9 each |
| **Pro** | 1 | $29 each |
| **Max** | 2 | $79 each |
Two kinds of stamps appear on the card:
* **Included passes** come with Pro and Max. They refresh every billing cycle, don't roll over, and are always consumed before purchased passes.
* **Purchased passes** are one-time purchases at the tier's price, bound to the tier they were bought for — a pass purchased on Pro is redeemable while you're on Pro. They never expire, so an unused pass applies again whenever you're back on that tier, even after a cancelled plan.
## Redeeming a pass [#redeeming-a-pass]
Click **Use a pass** on the card. Your premium usage for the week is zeroed immediately and the 7-day window restarts with your next premium request. Redemption is deliberately explicit — a pass is never consumed automatically, and it is rejected while your allowance is untouched. Once you've used more than 90% of your monthly credit allowance, the dashboard also asks you to confirm: a reset restores the weekly cap, but only the remaining monthly allowance is available to spend. Once the monthly allowance is fully used, Reset Passes cannot be redeemed.
Standard models are unaffected either way: they never count against the weekly cap.
## Renewal and the weekly window [#renewal-and-the-weekly-window]
Nothing about the weekly allowance carries over between billing cycles — every monthly renewal starts you completely fresh, all at once:
* **The weekly premium allowance resets in full.** Renewal zeroes your premium usage and starts a new 7-day window at the renewal moment, alongside the monthly credit reset. You never begin a cycle mid-window or with a partially used allowance.
* **Included passes replenish.** Pro's 1 and Max's 2 included passes are restored every cycle; unused ones don't roll over. Purchased passes are unaffected — they persist until redeemed.
An immediate tier upgrade restarts your billing cycle and performs the same full reset.
Between renewals the window is **rolling, not on a fixed schedule**. When a 7-day window ends, the next one starts with your next premium request — and a redeemed pass clears the window entirely, so your next premium request starts a fresh 7 days. There is no weekly reset schedule that redeeming late (or early) can shift into the next cycle.
In practice, a 30-day cycle of steady premium usage fits about five weekly windows (starting around days 0, 7, 14, 21, and 28) before any passes. On Max, whose weekly cap is 18% of the monthly allowance, those five windows plus a single included pass already unlock more than the entire monthly credit pool — so even spending exclusively on premium models needs no front-loaded pass redemptions or timing strategy. Redeem when you actually hit the cap.
If you hit the cap from a coding agent, the gateway's `402` response points here:
```json
{
"error": {
"message": "You've used your weekly allowance for premium-tier models on the pro plan. Redeem a Reset Pass from your dashboard for an instant reset, upgrade for a higher allowance, or use any standard model now. Resets in 6 days.",
"type": "invalid_request_error",
"code": "billing_error"
}
}
```
## Buying and billing [#buying-and-billing]
**Buy a pass** shows a confirmation dialog, then charges the payment method saved on your DevPass — no checkout redirect, no subscription change, no proration. Each purchase emails a PDF invoice and appears in the DevPass billing history alongside plan charges. Purchases are paused once you've used more than 95% of your monthly credit allowance — a pass would give you almost nothing to use at that point — and open up again when your credits renew.
An unused pass can be returned: for 7 days after purchase, a purchased pass that hasn't been redeemed can be self-refunded from the billing history. The refund goes back to your payment method, the pass is removed from your passport, and your DevPass plan itself is unaffected.
Reaching for a Reset Pass every week? Upgrading a tier is usually the better
deal — the next tier raises the weekly cap itself and brings a larger monthly
allowance. Compare on the [DevPass pricing
page](https://devpass.llmgateway.io/pricing).
# Lounge Memberships
URL: https://docs.llmgateway.io/learn/chat-plans
Lounge memberships (formerly Chat Plans) are optional monthly subscriptions for [Lounge](https://lounge.llmgateway.io), the LLM Gateway chat app. Instead of paying per request from your pay-as-you-go balance, a membership gives you a pool of monthly credits worth more than you pay — so heavy chat usage costs less.
## Plans [#plans]
There are three tiers, billed monthly:
| Plan | Price | Monthly value | Models |
| ----------- | ------ | ---------------- | ---------------------------------------------- |
| **Starter** | $9/mo | \~2× the value | Everyday chat models |
| **Plus** | $19/mo | \~2.5× the value | Everything in Starter **plus** frontier models |
| **Pro** | $49/mo | \~3× the value | All models, highest monthly allowance |
The credit multiplier is tapered: the larger the plan, the more usage value each dollar buys at provider rates.
**Frontier models** are included on **Plus** and **Pro**. Starter covers
everyday chat models. Browse the current [model
catalogue](https://llmgateway.io/models) and the Lounge model picker for
availability.
## How credits work [#how-credits-work]
* **Monthly reset** — Your plan credits refresh at the start of each billing cycle. Unused credits do **not** roll over to the next month.
* **Separate balances** — The **Chat plan** context uses your membership allowance. Your regular organizations keep their own pay-as-you-go credits.
* **Use existing credits** — When you view membership pricing, organizations with a positive available balance appear above the plans. Choose **Use these credits** to switch to that organization. You can also switch from the sidebar. This uses that organization’s billing context; it does not transfer credits into your membership.
## Managing your plan [#managing-your-plan]
* Open the **Pricing** page from the Lounge sidebar to compare tiers and subscribe.
* Your active membership appears in the Lounge sidebar with a badge, alongside how many credits remain for the cycle.
* **Upgrading** takes effect immediately: you're charged the new tier's full price, your billing cycle restarts, and your credits reset to the new tier's full monthly allowance. Unspent credits from the old cycle don't carry over.
* **Downgrading** also takes effect immediately: you move to the lower tier's allowance right away, and the unused portion of what you already paid for the higher tier is credited against your next invoice.
* **Cancelling** takes effect at the end of the period you've already paid for — you keep access until then.
## Refunds [#refunds]
A membership payment can be refunded from the billing history on the [Lounge pricing page](https://lounge.llmgateway.io/pricing) within 14 days, while less than 20% of its credits are used. Refunding ends the membership right away. See [Self-Service Refunds](https://docs.llmgateway.io/learn/refunds) for every condition.
# Audio Studio
URL: https://docs.llmgateway.io/learn/playground-audio
The Audio Studio turns text into speech using the available [audio models](https://llmgateway.io/models). Pick a model and a voice, type your text, and play or download the result.
## Model Selection [#model-selection]
Choose from the supported text-to-speech models in the dropdown. Each model has its own voice roster, output formats, and pricing — the newest models appear first.
## Generating Audio [#generating-audio]
1. Select a text-to-speech model
2. Pick a voice for the selected model
3. Type the text you want spoken — or click one of the sample prompts on the empty state
4. Click **Generate**
5. Generated clips appear in the gallery, where you can play or download them
## Voices [#voices]
The voice picker shows the voices supported by the selected model. Changing models updates the available voices and formats.
## Comparison Mode [#comparison-mode]
Enable comparison mode to send the same text to multiple models at once and hear the results side by side — useful for choosing a voice and provider before wiring up the [Speech Generation API](https://docs.llmgateway.io/features/speech-generation).
## History [#history]
Generated clips are saved to your audio history so you can revisit, replay, or delete them later.
## On iOS [#on-ios]
Open **Audio Studio** in The Lounge to generate speech, compare up to four models,
and adjust voices, formats, speed, and delivery instructions where supported.
Each comparison model uses its own defaults for unsupported voice or format choices.
Play the result, save it to Files, or open the native share sheet. Audio history
is shared with the web app and supports search, renaming, and deletion.
If history cannot be saved, **Retry saving audio** preserves the generated clips
without generating or charging for them again.
# Group Chat
URL: https://docs.llmgateway.io/learn/playground-group
[Group Chat](https://lounge.llmgateway.io/group) brings a council of models into one shared discussion. Models take turns reading the conversation and challenging the previous argument. For independent answers in separate panels, use comparison mode in the main chat instead.
## Start a discussion [#start-a-discussion]
1. Add 2–5 models from the model picker. They speak in selection order.
2. Enter a topic or choose a suggested question.
3. Click **Start discussion**. The first model makes a case; the next offers a counterargument.
4. Follow the streamed replies in one thread. Each round runs for five turns.
5. Choose **Stop discussion** at any time, **Continue discussion** for another round, or **New discussion** to start over.
Each response uses credits from the selected billing context. Group discussions stay in the current page and are not saved to chat history; leaving or reloading the page clears the discussion.
## Questions worth debating [#questions-worth-debating]
* **Decisions** — Explore tradeoffs before choosing an approach.
* **Brainstorming** — Ask models to challenge assumptions and suggest alternatives.
* **Research** — Surface competing explanations and questions to investigate. Verify factual claims independently.
# Image Studio
URL: https://docs.llmgateway.io/learn/playground-image
The Image Studio lets you generate images using AI models through an intuitive interface. Select a model, describe what you want, and get results instantly.
## Model Selection [#model-selection]
Choose from supported image generation models in the dropdown. Each model has different capabilities, resolutions, and pricing.
## Generating Images [#generating-images]
1. Select an image generation model
2. Type a description of the image you want
3. Click send to generate
4. Generated images appear in the conversation
## Image Count [#image-count]
You can generate 1, 2, or 4 images at once. Multiple images are displayed in a grid layout.
## Resolution Options [#resolution-options]
Available resolutions depend on the selected model. Common options include 1K, 2K, and 4K.
The gallery shows downscaled previews so a batch of high-resolution images
stays responsive; hover a tile to see its dimensions and file size. Click an
image to zoom into the full-resolution original.
## Downloading Images [#downloading-images]
Hover an image and click the download icon to save that image. Below a result
with several images, the **Download all** action bundles every image of that
generation into a single zip file.
## Flex Processing [#flex-processing]
Models that offer the Flex tier show a **Flex** toggle next to the image
count. Flex requests cost about half as much but run slower and are
best-effort under load. In comparison mode the tier applies only to the
selected models that support it. See
[Service Tiers](https://docs.llmgateway.io/features/service-tiers).
## Moderation [#moderation]
Models that expose a content-filtering control (GPT Image) show a **Moderation**
selector next to the quality control. `auto` applies the provider's standard
filtering; `low` is less restrictive. See
[Image Generation](https://docs.llmgateway.io/features/image-generation#moderation).
# Voice Calls
URL: https://docs.llmgateway.io/learn/playground-realtime
Voice Calls turns Lounge into a phone line to a model. Pick a realtime model and a voice, start the call, and talk — the model listens, answers out loud, and the transcript builds as you go. Every call runs over the same [Realtime API](https://docs.llmgateway.io/features/realtime) your own apps can use.
## Voice calls on iOS [#voice-calls-on-ios]
In **The Lounge**, open **Voice calls**, choose a realtime model and voice, and tap **Start voice call**. Allow microphone access when prompted. The model answers aloud while both sides of the conversation appear in the transcript. Use **Mute microphone** to pause sending your audio, or **End call** to finish. Leaving the screen or backgrounding the app also ends the call and saves it.
Open a saved call to read its transcript, replay assistant turns, or save and share their audio. Search, rename, and delete calls in **Call history**. **Continue this call**, where supported, restores the conversation as text and appends new turns to the same history entry. Gemini calls support reading and replaying saved sessions, but cannot continue them.
If saving fails, use **Retry saving call** before starting another call. The unsaved conversation remains available while the app stays open, including when you leave and return to the screen. Long calls stop storing additional audio at the storage limit; their text transcripts continue.
## Live transcription on iOS [#live-transcription-on-ios]
In **The Lounge** iOS app, open **Live transcription**, choose a model, and tap **Start transcription**. Allow microphone access when prompted. The selector shows active transcription-capable providers from the [model catalogue](https://llmgateway.io/models).
Use **Automatic speech detection** to complete turns when you pause, where supported. Otherwise, tap **Transcribe turn** after speaking. **Mute microphone** stops sending audio while keeping the session connected.
**Stop transcription** releases the microphone, submits an unfinished turn, and waits up to five seconds for pending text. **Stop now** ends that wait. Backgrounding the app also stops recording. Partial text remains visible if the connection fails or a turn cannot finish.
Use **Copy transcript** before leaving the screen. Transcription sessions are not saved to call history. **Clear transcript** removes the current text after the session ends.
## Starting a Call [#starting-a-call]
1. Pick a model from the selector — only models with an active realtime mapping appear, newest first
2. Pick a **Voice** — the voice list comes from the selected model, so it changes when you switch models
3. Click **Start call** and allow microphone access when the browser asks
The status label on the right walks through the handshake — preparing audio, requesting the microphone, authorizing, connecting, configuring — and settles on **Live** with a call timer next to it. The model and voice are locked for the duration of the call; end it to change either.
Your browser never sees an API key. The page mints a short-lived client secret server-side for the call, and the audio itself is streamed as 24 kHz PCM with echo cancellation and noise suppression on.
## During the Call [#during-the-call]
The model uses semantic voice activity detection, so there is no push-to-talk: stop speaking and it answers, start speaking again and it stops to listen. An interrupted answer stays in the transcript marked `(interrupted)`.
| Control | What it does |
| ------------------------ | ----------------------------------------------------------- |
| **Mute** / **Unmute** | Stops sending your microphone audio without ending the call |
| **End call** | Hangs up and saves the call to your history |
| Voice activity indicator | Shows who currently has the floor, and your mute state |
Below the controls, a usage line totals the call: how many responses it took, input and output tokens, and how many of those were audio tokens.
Voice calls are billed to a pay-as-you-go organization with credits, so the
organization switcher on this page only lists those — DevPass and Lounge
membership workspaces can't place calls. Without one selected, the page asks
you to create one in the dashboard first.
## Call History [#call-history]
Ended calls are saved per organization and listed in the sidebar, grouped by date, with each call's duration and time. Click one to reopen its transcript, along with the model and voice it used and its token totals. Use the **⋮** menu on a call to rename or delete it, and **New Call** to get back to a fresh screen.
Calls are titled from your opening line, so history reads like a list of topics. A call you leave mid-conversation — by navigating away or closing the tab — is still saved with whatever was said up to that point.
## Using It From Your Own App [#using-it-from-your-own-app]
The same endpoint backs the [Realtime API](https://docs.llmgateway.io/features/realtime): connect a WebSocket to `wss://api.llmgateway.io/v1/realtime`, authenticate with an [API key](https://docs.llmgateway.io/learn/api-keys), and use the standard OpenAI realtime event protocol. For browser clients, mint an ephemeral client secret from your backend instead of shipping a key:
```bash
curl -X POST "https://api.llmgateway.io/v1/realtime/client_secrets" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expires_after": { "anchor": "created_at", "seconds": 120 },
"session": { "type": "realtime", "model": "gpt-realtime" }
}'
```
Calls made here appear in your [activity feed](https://docs.llmgateway.io/learn/activity) and [analytics](https://docs.llmgateway.io/learn/analytics) like any other request, with audio input and output costs broken out.
# Video Studio
URL: https://docs.llmgateway.io/learn/playground-video
The Video Studio lets you generate videos using AI models. Select a model, describe what you want, and get video results.
## Model Selection [#model-selection]
Choose from supported video generation models in the dropdown. Each model has different capabilities, resolutions, and pricing.
## Generating Videos [#generating-videos]
1. Select a video generation model
2. Type a description of the video you want
3. Click send to generate
4. Generated videos appear in the conversation
## Generation settings [#generation-settings]
The controls under the prompt adapt to the selected model (or, in compare mode, to what every selected model supports):
* **Resolution** and **aspect ratio** are separate pickers. Only combinations the model accepts are offered, and the exact output size (for example 1920×1080) is shown next to them.
* **Duration** lists only the lengths the model supports. Some models allow fewer durations for image-to-video than for text-to-video, so the list changes once you add a frame.
* **Audio** can be toggled when the model supports both silent and audio output; otherwise it is fixed.
* **First frame** and **last frame** are offered only on models that accept them. A last frame requires a first frame, and models that only take a starting image do not show the last-frame button.
* **Reference images, videos and audio** appear on models that support them.
The estimated cost of the generation (summed across compared models) is shown next to the Generate button. Before submitting, the gateway requires your organization to have at least the estimated cost (and never less than $1.00) available after subtracting what its still-running video jobs have already reserved; otherwise the request is rejected before anything is generated.
## iOS client [#ios-client]
The iOS client under development includes Video Studio. Compare up to three models, choose supported size and duration settings, and add starting/ending frames or reference media where supported. Provider and region selections determine the available options.
Jobs continue in the background. Reopen a saved job from video history to check its status, play the result, or save/share the video through iOS. If saving a job to history fails, retry saving before starting another generation. Video availability depends on the provider's retention window.
# Lounge Chat
URL: https://docs.llmgateway.io/learn/playground
Lounge is a standalone chat app for talking to any LLM through a conversational interface. You can select any supported model, adjust parameters, and see responses in real time.
## Model Selection [#model-selection]
Use the dropdown at the top to pick a model and provider. The **Auto Route** option automatically selects the best provider based on availability and cost.
## Chat Interface [#chat-interface]
* Type your message in the input field at the bottom
* Click the send button or press Enter to submit
* Responses stream in real time
* Previous conversations appear in the sidebar
## Prompt Suggestions [#prompt-suggestions]
When starting a new chat, category tabs help you pick a prompt:
* **Create** — Content generation prompts
* **Explore** — Research and analysis prompts
* **Code** — Programming and development prompts
* **Image gen** — Image generation prompts
## Sidebar [#sidebar]
The left sidebar shows your chat history. Click **+ New Chat** to start a fresh conversation, or select a previous chat to continue it.
## Comparison Mode [#comparison-mode]
Toggle **Comparison mode** in the top-right to send the same prompt to multiple models side by side. For a shared debate where models respond to each other in sequence, open [Group Chat](https://docs.llmgateway.io/learn/playground-group).
## Image Studio [#image-studio]
Click **Image Studio** in the sidebar to switch to the image generation interface. See the [Image Studio](https://docs.llmgateway.io/learn/playground-image) page for details.
## Project knowledge [#project-knowledge]
Projects combine instructions, reference files, and memories for related conversations. File indexing, retrieval, and memory learning use the supplied workspace gateway key; requests without one use your personal Lounge workspace.
## Sandbox Escape [#sandbox-escape]
Choose a level and a model to play the grid-based Escape game. Each step makes one model request, billed to the selected workspace. Escape uses low reasoning effort when the selected model and provider support it. Collect the key fragments and reach the exit while avoiding the daemons. You can run continuously, pause, step once, or reset; completed runs have shareable replays and contribute to the model leaderboard.
Authenticated clients can call `POST /escape/move` on the account API with `projectId`, `levelId`, `moves`, and `model`. The API checks project access, reconstructs the board from the move history, and returns the next state and usage. It rejects finished runs and does not automatically retry model requests. An unparseable model reply consumes a wait turn. Save completed runs with `POST /escape/runs`; `GET /escape/runs` lists your saved runs with optional `organizationId`, `limit`, and `offset` filters.
## iOS app development [#ios-app-development]
The native iOS client lives in `apps/mobile` in the repository and is under development. It uses the same LLM Gateway account and Lounge data. Membership purchases and billing stay on the Lounge website.
Sign in with your LLM Gateway email and password, or create an account and verify your email before returning to sign in. **Forgot password?** emails a link to the account website, where you can choose a new password. To permanently delete your account across LLM Gateway products, open **Your profile → Delete account**, review the affected workspaces, and confirm deletion.
Choose **Sign in with browser** to use the website's passkey, social sign-in, or SSO options. Note the code shown in the app, choose **Continue in browser**, and approve only if the website shows the same code. The app returns automatically after approval and saves its separate session in iOS Keychain. Cancelling or denying the request leaves the app signed out; choose **Get a new code** to start again.
If you need an account, choose **Sign up** in the browser. Email verification returns to the device approval page. If you verify in a different browser, sign in in the app's browser to finish approval. Get a new code in the app if the original expires while you create your account.
Use **Switch workspace** to choose where conversations and usage belong. The home screen shows the selected workspace, and the app restores that workspace and its project after restart. If access changes or restoration fails, retry or explicitly choose another workspace before continuing. Signing out clears the device's saved selection.
Conversations support Markdown, reasoning, attachments, copying, editing, retries, and forking. Chat settings include system instructions, temperature, output limits, reasoning effort, and web search where supported by the model. Settings stay on the device until sign-out. Temporary conversations stay out of Lounge history; normal gateway usage and organization retention settings still apply.
The model picker supports favorites and provider selection. Choosing a provider pins requests to that provider without fallback; choosing the model itself or Auto keeps automatic provider routing. Favorites sync with your account. The model and provider lists scroll with their headings, so choices remain reachable at larger iOS text sizes. At larger text sizes or in a short window, conversation controls also scroll above the messages. Composer buttons wrap to fit the available width.
Web search sources appear below the response and remain available in saved history. Share a conversation snapshot publicly or with your workspace, choose whether readers can fork it, and revoke its link later. Shared conversations lets you browse workspace snapshots or open a pasted Lounge share link.
Model comparisons send the same prompt and attachments to up to three models, with separate saved conversations for each. Switch between model tabs, retry one response, stop all models, or open a panel as a normal conversation. If saving a response fails, retry saving it without generating it again. Connected apps are available to Model 1 with explicit approval; see [Lounge connectors](https://docs.llmgateway.io/features/lounge-connectors) for approval and recovery behavior.
Group discussions let two to five models take five turns debating a topic. Stop and continue the discussion or share its transcript. Group discussions stay on the open screen and are not added to conversation history.
The iOS Image Studio supports reference-image edits, model comparison, model-specific size and quality settings, and synchronized history. Save images to Files or share them through the iOS share sheet. If saving history fails, the generated images remain available and you can retry saving without generating them again.
Choose **System**, **Light**, or **Dark** in the iOS profile's **Appearance** controls or on the sign-in screen. System follows the device setting. The preference is saved on the device and stays after sign-out. Changing appearance preserves open conversations and interactive Canvas state.
In **Your profile**, track your level progress, activity points, daily earnings, and streaks. Choose a public username and join the member leaderboard to share your name, picture, level, and points; your conversations stay private. You can hide your picture or make your profile private again. **View leaderboard** shows the top 100 public members. Your global points rank also includes private members.
In **Sandbox Escape**, choose a level and model, then run continuously or take one step at a time. Leaving the screen or backgrounding the app pauses the run after its current turn. Completed runs are saved to your workspace; retry a failed save before starting a new run. Browse saved runs, open a shared replay link, scrub through the board without model calls, and view rankings across all levels or one level. Your Escape model choice stays on the device until sign-out.
In **Canvas**, describe an interface or start from a template. Choose a model, interact with the generated preview, or edit and apply its JSON. Stopping generation keeps the latest rendered preview. Export a PNG or PDF to Files or the iOS share sheet before leaving; canvases are not saved to conversation history. Your selected Canvas model stays on the device until sign-out.
Its conversation history searches titles and message text within your selected workspace. Active and archived conversations have separate views, and you can load more results for longer histories. Archiving hides a conversation from the active list; restoring brings it back. Deleting a conversation is permanent.
Projects in the iOS app keep instructions, knowledge files, memories, and related conversations together. Upload PDF, XLSX, text, Markdown, or source-code files in **Projects**. Starting a project conversation retrieves relevant file excerpts alongside its instructions and memories. Successful saved replies can add durable memories automatically; temporary conversations and interrupted replies do not. You can add, edit, and delete memories yourself. Deleting a project removes its files and memories while keeping its conversations without project context.
In **Skills**, create reusable instructions or choose **Generate skill** to describe what you need. Generation uses your selected workspace’s gateway key; API requests without an explicit key use your personal Lounge workspace. Review and edit the generated draft, then save it. Enabled skills apply across your workspaces. You can edit, disable, or delete them; disabled and deleted skills are excluded from subsequent requests.
See the [mobile README](https://github.com/theopenco/llmgateway/tree/main/apps/mobile) for build instructions and the feature verification checklist. TestFlight availability will be announced when the app is ready.
# Health check
URL: https://docs.llmgateway.io/health
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create speech
URL: https://docs.llmgateway.io/v1_audio_speech
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create transcription
URL: https://docs.llmgateway.io/v1_audio_transcriptions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Chat Completions
URL: https://docs.llmgateway.io/v1_chat_completions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Decisions
URL: https://docs.llmgateway.io/v1_decisions
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Embeddings
URL: https://docs.llmgateway.io/v1_embeddings
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Edit image
URL: https://docs.llmgateway.io/v1_images_edits
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create image
URL: https://docs.llmgateway.io/v1_images_generations
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Retrieve key status
URL: https://docs.llmgateway.io/v1_key_retrieve
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Anthropic Messages
URL: https://docs.llmgateway.io/v1_messages
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Models
URL: https://docs.llmgateway.io/v1_models
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Moderations
URL: https://docs.llmgateway.io/v1_moderations
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# OCR
URL: https://docs.llmgateway.io/v1_ocr
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create a realtime client secret
URL: https://docs.llmgateway.io/v1_realtime_client_secrets
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Rerank
URL: https://docs.llmgateway.io/v1_rerank
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Search
URL: https://docs.llmgateway.io/v1_search
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# System One
URL: https://docs.llmgateway.io/v1_systemone
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Video content
URL: https://docs.llmgateway.io/v1_videos_content
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Create video
URL: https://docs.llmgateway.io/v1_videos_create
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Video log content
URL: https://docs.llmgateway.io/v1_videos_log_content
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Retrieve video
URL: https://docs.llmgateway.io/v1_videos_retrieve
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# AI SDK Gateway protocol
URL: https://docs.llmgateway.io/developers/ai-sdk-gateway-protocol
When you pass a bare model string to the AI SDK — `streamText({ model: "anthropic/claude-sonnet-5" })` — the SDK resolves it through its **default provider**, `@ai-sdk/gateway`. That provider does not speak the OpenAI Chat Completions format: it has its own wire protocol, `LanguageModelV*CallOptions` in and `LanguageModelV*` parts out.
LLM Gateway implements that protocol, so an app written against the Vercel AI Gateway runs here with **no code change** — only its base URL and API key are repointed.
## Repoint the default provider [#repoint-the-default-provider]
```ts
import { createGateway } from "@ai-sdk/gateway";
globalThis.AI_SDK_DEFAULT_PROVIDER = createGateway({
baseURL: "https://api.llmgateway.io/v4/ai",
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
```
Put this wherever your app runs before its first model call — a Next.js [`instrumentation.ts`](https://nextjs.org/docs/app/guides/instrumentation), a server entrypoint, or a platform-injected preamble. Every bare model string in the app then resolves through LLM Gateway.
`@ai-sdk/gateway` is already a transitive dependency of `ai`, so there is nothing extra to install.
`@ai-sdk/gateway` reads its API key from `AI_GATEWAY_API_KEY` but has **no**
environment variable for the base URL — it is a constructor option only. That
is why repointing takes this one line rather than an env var.
Or construct the provider explicitly and pass it per call:
```ts
import { createGateway } from "@ai-sdk/gateway";
import { streamText } from "ai";
const gateway = createGateway({
baseURL: "https://api.llmgateway.io/v4/ai",
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const result = streamText({
model: gateway("anthropic/claude-sonnet-5"),
prompt: "Hello!",
});
```
## Base URL per AI SDK version [#base-url-per-ai-sdk-version]
The protocol carries the language-model specification version in a request header, and every prefix below serves the same surface — pick the one matching the `@ai-sdk/gateway` your app has, so the default path stays intact:
| AI SDK | Spec version | Base URL |
| ------ | ------------ | --------------------------------- |
| 5 | 2 | `https://api.llmgateway.io/v1/ai` |
| 6 | 3 | `https://api.llmgateway.io/v3/ai` |
| 7 | 4 | `https://api.llmgateway.io/v4/ai` |
## Model IDs [#model-ids]
Model IDs use the provider-pinned `provider/model` form (`anthropic/claude-sonnet-5`, `openai/gpt-4o`) — the same convention AI Gateway IDs use, so existing model strings resolve unchanged.
LLM Gateway's smart-routing IDs work here too: pass a bare model ID (`gpt-4o`) to let the gateway pick a provider, or `auto` to let it pick the model. Those are not returned by `getAvailableModels()` because they do not name one provider, but they are accepted.
## Listing models [#listing-models]
```ts
const { models } = await gateway.getAvailableModels();
```
Returns one entry per active provider mapping, with pricing and an AI SDK `specification` block. This is what backs a model picker built on `GatewayModel[]`.
## Credits [#credits]
```ts
const { balance, totalUsed } = await gateway.getCredits();
```
`balance` is the organization's remaining credit balance and `totalUsed` its lifetime credits spend.
## Gateway-only options [#gateway-only-options]
Features that have no field in the AI SDK call options — reasoning effort, service tier, routing strategy, prompt cache keys, plugins — are set through the `llmgateway` provider options namespace, which is passed onto the underlying request:
```ts
const result = streamText({
model: gateway("openai/gpt-5.6-terra"),
prompt: "Hello!",
providerOptions: {
llmgateway: {
reasoning_effort: "high",
routing: "price",
},
},
});
```
Any field the [chat completions API](https://docs.llmgateway.io/api-reference) accepts works here, except `model`, `messages` and `stream`, which this surface owns.
## Web search [#web-search]
The provider-native web search tools serialize to provider-defined tools, and the gateway maps them onto its [native web search](https://docs.llmgateway.io/features/web-search):
```ts
import { openai } from "@ai-sdk/openai";
const result = streamText({
model: gateway("openai/gpt-4o"),
prompt: "What happened in the news today?",
tools: { web_search: openai.tools.webSearch() },
});
```
Recognised tools: `openai.web_search`, `openai.web_search_preview`, `anthropic.web_search_20250305`, `anthropic.web_search_20260209`, `google.google_search`. Search results come back as `source-url` message parts plus a provider-executed tool call under the name you bound the tool to, so the AI SDK's sources UI works unchanged.
A provider-defined tool the gateway cannot map is reported as an `unsupported-tool` warning on the result rather than failing the request.
## What is not covered [#what-is-not-covered]
This surface implements language models. Embeddings, images, video, speech, transcription, reranking and realtime are served by the [OpenAI-compatible endpoints](https://docs.llmgateway.io/api-reference) — use [`@llmgateway/ai-sdk-provider`](https://docs.llmgateway.io/developers/ai-sdk) for those.
These call options have no chat completions equivalent and are reported as `unsupported` warnings: `stopSequences`, `seed`, `topK`.
# Image Generation with the AI SDK
URL: https://docs.llmgateway.io/developers/ai-sdk-images
The `@llmgateway/ai-sdk-provider` package supports image generation both through the AI SDK's dedicated `generateImage` function and through chat-based image models that stream images as part of a conversation.
## generateImage [#generateimage]
Use `llmgateway.image()` to get an image model:
```typescript
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
import { generateImage } from "ai";
import { writeFileSync } from "fs";
const llmgateway = createLLMGateway({
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const result = await generateImage({
model: llmgateway.image("gemini-3-pro-image"),
prompt:
"A cozy cabin in a snowy mountain landscape at night with aurora borealis",
size: "1024x1024",
n: 1,
// aspectRatio and quality are model-specific — only some providers honor them.
// aspectRatio works on Gemini image models; OpenAI gpt-image-2 ignores it
// (use a literal WxH `size` instead).
aspectRatio: "16:9",
// quality works on OpenAI gpt-image-2 ("low" | "medium" | "high" | "auto")
// and moderation ("auto" | "low") on GPT Image models.
// The AI SDK only forwards these through providerOptions.
providerOptions: {
llmgateway: { quality: "high", moderation: "low" },
},
});
result.images.forEach((image, i) => {
const buf = Buffer.from(image.base64, "base64");
writeFileSync(`image-${i}.png`, buf);
});
```
Which sizes, aspect ratios, and quality settings a model accepts depends on
the model — see [Image Generation](https://docs.llmgateway.io/features/image-generation) for the full
parameter reference and per-model behavior.
## Chat-based image models [#chat-based-image-models]
Multimodal models like `gemini-3-pro-image` can return images inside a chat conversation. Use `llmgateway.chat()` with `streamText` in a route handler:
```typescript
// app/api/chat/route.ts
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
import { convertToModelMessages, streamText } from "ai";
const llmgateway = createLLMGateway({
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: llmgateway.chat("gemini-3-pro-image"),
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}
```
On the client, image parts arrive as message file parts that you can render with the AI Elements `Image` component or a plain `` tag with a data URL. See [Image Generation](https://docs.llmgateway.io/features/image-generation) for the complete `useChat` frontend example.
## Video and audio [#video-and-audio]
The AI SDK does not yet cover the gateway's video and speech endpoints — call them over REST instead:
* [Video Generation](https://docs.llmgateway.io/features/video-generation) — `POST /v1/videos` (async jobs with optional signed callbacks)
* [Speech Generation](https://docs.llmgateway.io/features/speech-generation) — `POST /v1/audio/speech`
* [Transcription](https://docs.llmgateway.io/features/transcription) — `POST /v1/audio/transcriptions`
# Using the AI SDK
URL: https://docs.llmgateway.io/developers/ai-sdk
LLM Gateway ships a first-party provider for the [Vercel AI SDK](https://ai-sdk.dev): [`@llmgateway/ai-sdk-provider`](https://github.com/theopenco/llmgateway-ai-sdk-provider). One provider instance and one API key reach every model in the catalog.
## Install [#install]
```bash
pnpm add ai @llmgateway/ai-sdk-provider
```
Set your API key (create one from the [dashboard](https://llmgateway.io/dashboard)):
```bash
export LLM_GATEWAY_API_KEY=llmgtwy_your_key_here
```
## Generate text [#generate-text]
```typescript
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
import { generateText } from "ai";
const llmgateway = createLLMGateway({
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const { text } = await generateText({
model: llmgateway("openai/gpt-4o"),
prompt: "Hello!",
});
```
Switching models is a one-line change — the same provider serves every model:
```typescript
const { text } = await generateText({
model: llmgateway("anthropic/claude-3-5-sonnet-20241022"),
prompt: "Hello!",
});
```
## Model ID formats [#model-id-formats]
LLM Gateway supports two model ID formats:
* **Canonical model IDs** (`gpt-4o`) — smart routing picks the best provider based on uptime, throughput, price, and latency
* **Provider-prefixed IDs** (`openai/gpt-4o`) — routes to a specific provider with automatic failover if uptime drops below 90%
See the [routing documentation](https://docs.llmgateway.io/features/routing) for details and the [models page](https://llmgateway.io/models) for the full catalog.
## Stream responses [#stream-responses]
```typescript
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
import { streamText } from "ai";
const llmgateway = createLLMGateway({
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const { textStream } = await streamText({
model: llmgateway("anthropic/claude-3-5-sonnet-20241022"),
prompt: "Write a poem about coding",
});
for await (const text of textStream) {
process.stdout.write(text);
}
```
## Next.js route handler [#nextjs-route-handler]
```typescript
// app/api/chat/route.ts
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
import { streamText } from "ai";
const llmgateway = createLLMGateway({
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: llmgateway("openai/gpt-4o"),
messages,
});
return result.toDataStreamResponse();
}
```
## Tool calling [#tool-calling]
```typescript
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
import { generateText, tool } from "ai";
import { z } from "zod";
const llmgateway = createLLMGateway({
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const { text, toolResults } = await generateText({
model: llmgateway("openai/gpt-4o"),
tools: {
weather: tool({
description: "Get the weather for a location",
parameters: z.object({
location: z.string(),
}),
execute: async ({ location }) => {
return { temperature: 72, condition: "sunny" };
},
}),
},
prompt: "What's the weather in San Francisco?",
});
```
## Without the provider package [#without-the-provider-package]
If you prefer not to add a dependency, point `@ai-sdk/openai` at the gateway with a custom base URL:
```typescript
import { createOpenAI } from "@ai-sdk/openai";
import { generateText } from "ai";
const llmgateway = createOpenAI({
baseURL: "https://api.llmgateway.io/v1",
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const { text } = await generateText({
model: llmgateway("openai/gpt-4o"),
prompt: "Hello!",
});
```
Every request made through the AI SDK shows up in your
[Activity](https://docs.llmgateway.io/learn/activity) and [Usage & Metrics](https://docs.llmgateway.io/learn/usage-metrics)
dashboards like any other gateway request — with per-request cost, tokens, and
latency.
## Generate video [#generate-video]
Version 4 of the provider (AI SDK 7, Node.js 22 or later, ESM) adds `llmgateway.video()` for the SDK's experimental video API. The provider submits a gateway video job, the SDK polls it, and the finished file is downloaded from the authenticated content endpoint.
```typescript
import { writeFile } from "node:fs/promises";
import { llmgateway } from "@llmgateway/ai-sdk-provider";
import { experimental_generateVideo as generateVideo } from "ai";
const { video } = await generateVideo({
model: llmgateway.video("seedance-2-0"),
prompt: "A cinematic aerial view of ocean waves at sunrise",
duration: 8,
resolution: "1280x720",
poll: { intervalMs: 5_000, timeoutMs: 600_000 },
});
await writeFile("video.mp4", video.uint8Array);
```
`duration` and a text prompt are required. `resolution` maps to the gateway's `size`, `duration` to `seconds`, and `generateAudio` to `audio`. Pass a first frame as `prompt.image`, first and last frames with `frameImages`, and reference inputs with `inputReferences`; any other gateway field, such as `callback_url`, goes through `providerOptions.llmgateway`. `experimental_startVideo` and `experimental_getVideoStatus` let another process pick up a running job. Supported sizes, durations, and inputs depend on the model — see [video generation](https://docs.llmgateway.io/features/video-generation) for the REST reference. Projects on AI SDK 6 should stay on `@llmgateway/ai-sdk-provider@3`, which has no video support.
## Next steps [#next-steps]
* [Image generation with the AI SDK](https://docs.llmgateway.io/developers/ai-sdk-images)
* [Migrate from Vercel AI Gateway](https://docs.llmgateway.io/migrations/vercel-ai-gateway)
* [Reasoning support](https://docs.llmgateway.io/features/reasoning) and [caching](https://docs.llmgateway.io/features/caching)
# LLM Gateway CLI
URL: https://docs.llmgateway.io/developers/cli
The **LLM Gateway CLI** (`@llmgateway/cli`) is a command-line utility for launching coding agents pre-configured with LLM Gateway, scaffolding projects, discovering models, and managing your LLM Gateway account — API keys, spending budgets, and usage analytics — straight from the terminal.
**Using DevPass?** This integration also works with a
[DevPass](https://devpass.llmgateway.io) plan key. Use canonical model IDs
without a provider prefix (`claude-sonnet-4-5`, not
`anthropic/claude-sonnet-4-5`) — provider-pinned routing is not available on
coding plans; the gateway picks the provider for you.
## Installation [#installation]
Run commands directly without installation:
```bash
npx @llmgateway/cli init
```
Install globally for faster access:
```bash
npm install -g @llmgateway/cli
```
Then run commands directly (`lg` works as a shorthand alias):
```bash
llmgateway init
lg init
```
## Quick Start [#quick-start]
### Initialize a Project [#initialize-a-project]
Create a new project from a template:
```bash
npx @llmgateway/cli init
```
Or specify the template and name directly:
```bash
npx @llmgateway/cli init --template image-generation --name my-ai-app
```
### Sign In [#sign-in]
Sign in with your LLM Gateway account to unlock key management, budgets, and usage analytics:
```bash
npx @llmgateway/cli auth login --email you@example.com
```
Or store a gateway API key only (enough for making gateway requests):
```bash
npx @llmgateway/cli auth login --key
```
Credentials are stored in `~/.llmgateway/config.json`. The `LLMGATEWAY_API_KEY` environment variable takes precedence over a stored key.
### Start Development [#start-development]
Navigate to your project and start the development server:
```bash
cd my-ai-app
npx @llmgateway/cli dev
```
Or specify a custom port:
```bash
npx @llmgateway/cli dev --port 3000
```
## Launch Coding Agents [#launch-coding-agents]
### `launch` [#launch]
Start any supported coding agent pre-wired to LLM Gateway: one API key, 200+ models, and every request tracked in your [dashboard](https://llmgateway.io/dashboard).
```bash
# Interactive agent picker
npx @llmgateway/cli launch
# Launch a specific agent (shortcuts work too: `llmgateway claude`)
npx @llmgateway/cli launch claude
npx @llmgateway/cli launch opencode
npx @llmgateway/cli launch codex
# Pick a model — launcher flags go before the agent name
npx @llmgateway/cli launch -m gpt-5.5 claude
# Everything after the agent name is passed to the agent itself
npx @llmgateway/cli launch claude --continue
# List all supported agents and see which are installed
npx @llmgateway/cli launch --list
# Inspect what would run without launching
npx @llmgateway/cli launch --dry-run codex
```
Every supported agent also works as a direct shortcut, e.g. `npx @llmgateway/cli claude` or `npx @llmgateway/cli devpass-code`. The launcher configures each agent automatically — environment variables, config files, or the agent's own key-registration command, whichever that agent needs — without overwriting your existing setup. For OpenCode and Claude Code, launching also applies the same model-catalog setup as [`configure`](#configure) on every launch, so their model pickers stay fresh as new models ship.
The API key is resolved from `--key`, the `LLMGATEWAY_API_KEY` environment variable, or the key stored by `llmgateway auth login --key` — in that order. Before launching, the key is verified against the gateway; a stale key (e.g. one you rolled or deleted) is reported with its exact source and the launcher falls back to the next valid one, prompting you for a fresh key if none works. If an agent isn't installed, the launcher prints its official install command and exits.
See the [integration guides](https://llmgateway.io/guides) for per-agent setup
details, and run `npx @llmgateway/cli launch --list` for the up-to-date list
of supported agents.
### `configure` [#configure]
Put LLM Gateway's coding-model catalog directly into an agent's own config, so its model picker lists gateway models without launching through the CLI:
```bash
# opencode: adds every coding model, pinned per upstream provider, to the picker
npx @llmgateway/cli configure opencode
# Claude Code: routes it through LLM Gateway and fills /model from the gateway catalog
npx @llmgateway/cli configure claude
# ...for the current repo only (.claude/settings.local.json)
npx @llmgateway/cli configure claude --project
# Preview without writing
npx @llmgateway/cli configure opencode --dry-run
```
* **OpenCode** — merges `provider/model` entries (e.g. `anthropic/claude-sonnet-5`, `aws-bedrock/claude-sonnet-5`) into `provider.llmgateway.models` in `~/.config/opencode/opencode.json`, with display names, context limits, and per-provider pricing. They show up in the picker as `llmgateway//` and pin that upstream provider via the gateway's [provider-routing syntax](https://docs.llmgateway.io/features/routing#provider-specific-routing). OpenCode now ships both catalogs natively too — canonical IDs under **DevPass (LLM Gateway)** (`llmgateway`) and pinned `provider/model` IDs under **LLM Gateway** (`llmgateway-providers`) — so `configure` mainly covers models the built-in catalog has not picked up yet. Existing custom entries and the rest of the file are preserved, and a hand-written `opencode.jsonc` keeps working alongside it.
* **Claude Code** — sets `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, and `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1` in `~/.claude/settings.json` (requires Claude Code v2.1.129+). Claude Code then loads the gateway's `/v1/models` catalog into its `/model` picker (shown as "From gateway"). Claude Code only lists IDs starting with `claude`/`anthropic`; any other gateway model still works via `claude --model `.
`llmgateway launch opencode` and `llmgateway launch claude` apply the same setup automatically on every launch, keeping the catalog fresh as new models ship.
## Project Commands [#project-commands]
### `init` [#init]
Initialize a new project from a template.
```bash
npx @llmgateway/cli init [directory] [options]
```
**Options:**
* `-t, --template ` — Template to use (default: `image-generation`)
* `-n, --name ` — Project name
**Examples:**
```bash
# Interactive mode
npx @llmgateway/cli init
# With options
npx @llmgateway/cli init --template image-generation --name my-app
```
### `list` [#list]
Display available project templates, grouped by category. Alias: `ls`.
```bash
npx @llmgateway/cli list
```
**Options:**
* `--json` — Output in JSON format
### `models` [#models]
Browse and filter available AI models.
```bash
npx @llmgateway/cli models [options]
```
**Options:**
* `-c, --capability ` — Filter by capability (e.g., `image`, `text`)
* `-p, --provider ` — Filter by provider (e.g., `openai`, `anthropic`)
* `-s, --search ` — Search models by name
* `--json` — Output in JSON format
**Examples:**
```bash
# List all models
npx @llmgateway/cli models
# Filter by provider
npx @llmgateway/cli models --provider openai
# Search models
npx @llmgateway/cli models --search gpt
```
### `add` [#add]
Add tools or API routes to an existing project.
```bash
npx @llmgateway/cli add [type] [name]
```
Runs interactively when `type` (`tool` or `route`) and `name` are omitted.
**Tools available:**
* `weather` — Weather lookup functionality
* `search` — Web search capability
* `calculator` — Mathematical operations
**API routes available:**
* `generate` — Text generation endpoint
* `chat` — Chat completion endpoint with streaming
### `dev` [#dev]
Start the local development server using your project's package manager.
```bash
npx @llmgateway/cli dev [options]
```
**Options:**
* `-p, --port ` — Port to run on
### `upgrade` [#upgrade]
Update LLM Gateway dependencies (`@llmgateway/ai-sdk-provider`, `@llmgateway/models`, `@llmgateway/cli`) in your project.
```bash
npx @llmgateway/cli upgrade [options]
```
**Options:**
* `--check` — Check for updates without installing
### `docs` [#docs]
Open the documentation in your browser.
```bash
npx @llmgateway/cli docs [topic]
```
**Topics:** `models`, `api`, `sdk`, `quickstart` — omit to open the docs home and see all topics.
## Account Commands [#account-commands]
The commands below require a dashboard session — sign in first with
`llmgateway auth login --email`. A gateway API key alone is not enough for
account management.
### `auth` [#auth]
Manage authentication (dashboard session and gateway API key).
For browser-based device sign-in, the approval page is titled **Authorize your device**. Check that its code matches the one on your device before choosing **Authorize device**. Approval creates a separate account session; signing the device out leaves the browser signed in. Choose **Deny** for a request you did not start.
```bash
# Sign in with email & password (full access), or paste an API key
npx @llmgateway/cli auth login
npx @llmgateway/cli auth login --email you@example.com
npx @llmgateway/cli auth login --key
# Check authentication status (session + API key)
npx @llmgateway/cli auth status
# Show the signed-in user
npx @llmgateway/cli auth whoami
# Remove stored session and API key
npx @llmgateway/cli auth logout
```
### `keys` [#keys]
Create and manage gateway API keys.
```bash
npx @llmgateway/cli keys
```
#### `keys create` [#keys-create]
Create a new API key, optionally with spending limits and an expiry.
```bash
npx @llmgateway/cli keys create --description "CI key" --limit 100 --expires 30d
```
**Options:**
* `-p, --project ` — Project the key belongs to
* `-d, --description ` — Key description
* `-l, --limit ` — Total spending limit in USD (e.g. `100` or `49.99`)
* `--period-limit ` — Spending limit per rolling period in USD
* `--period ` — Rolling period for `--period-limit` (`12h`, `1d`, `2w`, `1mo`; default `1mo`)
* `-e, --expires ` — TTL as a duration (`30d`, `12h`) or an ISO date
* `--json` — Output in JSON format
The token is only displayed once at creation time — save it immediately.
#### `keys list` [#keys-list]
List API keys with spend, budget, and expiry. Alias: `keys ls`.
**Options:**
* `-p, --project ` — Filter by project
* `--all` — Show all keys in the org (admin/owner only)
* `--json` — Output in JSON format
#### `keys update ` [#keys-update-id]
Activate or deactivate an API key.
**Options:**
* `--activate` — Set the key to active
* `--deactivate` — Set the key to inactive
* `-e, --expires ` — New expiry as a duration (`30d`) or ISO date (needed to reactivate expired keys)
#### `keys limit ` [#keys-limit-id]
Set spending limits on an API key (same as `budget set`).
**Options:**
* `-l, --limit ` — Total spending limit in USD
* `--period-limit ` — Spending limit per rolling period in USD
* `--period ` — Rolling period (`12h`, `1d`, `2w`, `1mo`; default `1mo`)
* `--clear` — Remove all spending limits
#### `keys roll ` [#keys-roll-id]
Regenerate the token for an API key. The old token becomes invalid immediately.
**Options:**
* `-y, --yes` — Skip confirmation
#### `keys delete ` [#keys-delete-id]
Delete an API key. Alias: `keys rm`.
**Options:**
* `-y, --yes` — Skip confirmation
### `budget` [#budget]
Manage API key spending limits.
```bash
# Set a total and/or rolling-period budget
npx @llmgateway/cli budget set --limit 100 --period-limit 25 --period 1w
# Remove all spending limits
npx @llmgateway/cli budget set --clear
# Show budget and current spend
npx @llmgateway/cli budget get
```
**`budget set` options:** `-l, --limit `, `--period-limit `, `--period `, `--clear`
**`budget get` options:** `-p, --project `, `--json`
### `usage` [#usage]
View usage and cost analytics.
```bash
npx @llmgateway/cli usage [options]
```
**Options:**
* `-o, --org ` — Aggregate usage across an organization
* `-p, --project ` — Filter by project
* `-k, --api-key ` — Filter by API key
* `--by ` — Break down by `model` or `key`
* `-r, --range ` — Time range: `1h`, `4h`, `24h`, `7d`, `30d`, `365d` (default `7d`)
* `--days ` — Look back N days instead of `--range`
* `--from ` / `--to ` — Custom date range (`YYYY-MM-DD`)
* `--json` — Output in JSON format
**Examples:**
```bash
# Last 7 days for the default project
npx @llmgateway/cli usage
# Cost per model over the last 30 days
npx @llmgateway/cli usage --by model --range 30d
# Whole-org aggregate
npx @llmgateway/cli usage --org
```
#### `usage sources` [#usage-sources]
Break down usage by session/agent source to see which agents or sessions are spending.
```bash
npx @llmgateway/cli usage sources [options]
```
**Options:** `-p, --project `, `-r, --range ` (`7d`, `30d`), `--from `, `--to `, `--json`
### `orgs` [#orgs]
List your organizations with plan and credit balance. Alias: `orgs ls`.
```bash
npx @llmgateway/cli orgs list [--json]
```
### `projects` [#projects]
Manage projects and the CLI's default project.
```bash
# List projects (optionally filtered by org)
npx @llmgateway/cli projects list [--org ] [--json]
# Set the default project used by keys/budget/usage commands
npx @llmgateway/cli projects use
```
### `credits` [#credits]
Show organization credit balances.
```bash
npx @llmgateway/cli credits [--org ] [--json]
```
## Available Templates [#available-templates]
### Web Applications [#web-applications]
* **`embeddable-credits`** — Monetize your AI app in 5 minutes. End-user wallets and in-app credit purchases ("Stripe for AI") with the embeddable SDK.
* **`image-generation`** — Full-stack AI image generation app (Next.js 16, React 19). Multi-provider support with a unified API.
* **`ai-chatbot`** — AI chatbot with streaming responses.
* **`og-image-generator`** — AI-powered OG image generator.
* **`feedback-dashboard`** — Customer feedback sentiment dashboard.
* **`writing-assistant`** — AI writing assistant with text actions.
* **`qa-agent`** — AI-powered QA testing agent with browser automation, real-time action timeline, and live browser preview.
* **`showcase`** — Public, filterable gallery of apps built with LLM Gateway templates. Static and deployable, with a "Submit your app" flow.
### Bots [#bots]
* **`slack-qa-bot`** — Slack bot that streams AI answers and keeps thread context.
### CLI Agents [#cli-agents]
* **`weather-agent`** — Answers weather queries using tool calling.
* **`lead-agent`** — Researches people and posts results to Discord.
* **`changelog-generator-agent`** — Generates changelogs from git history.
* **`email-drafter-agent`** — Drafts polished emails from rough notes.
* **`sentiment-analyzer-agent`** — Analyzes text sentiment.
* **`data-extractor-agent`** — Extracts structured entities from text.
```bash
npx @llmgateway/cli init --template qa-agent
```
## Configuration [#configuration]
The CLI stores configuration in `~/.llmgateway/config.json`:
```json
{
"apiKey": "llmgtwy_...",
"defaultTemplate": "image-generation",
"sessionEmail": "you@example.com",
"defaultOrgId": "org_...",
"defaultProjectId": "proj_..."
}
```
Signing in with `auth login --email` also stores a dashboard session used by the account commands (`keys`, `budget`, `usage`, `orgs`, `projects`, `credits`).
### Environment Variables [#environment-variables]
* `LLMGATEWAY_API_KEY` — Gateway API key; takes precedence over the config file:
```bash
export LLMGATEWAY_API_KEY="llmgtwy_..."
```
* `LLMGATEWAY_API_URL` — Override the management API base URL (defaults to `https://internal.llmgateway.io`), useful for self-hosted deployments.
## More Resources [#more-resources]
* [Agents](https://llmgateway.io/agents) — Pre-built AI agents
* [Templates](https://llmgateway.io/templates) — Production-ready starter projects
* [GitHub Repository](https://github.com/theopenco/llmgateway-templates) — Source code and issues
Need help or want to request a feature? Open an issue on
[GitHub](https://github.com/theopenco/llmgateway-templates/issues).
# LLM Gateway Developer Resources
URL: https://docs.llmgateway.io/developers
This section is for developers building applications on top of LLM Gateway — with our command-line tool, the MCP server, the [Vercel AI SDK](https://ai-sdk.dev) via our first-party provider package [`@llmgateway/ai-sdk-provider`](https://github.com/theopenco/llmgateway-ai-sdk-provider), and [TanStack AI](https://tanstack.com/ai) via the first-party [`@tanstack/ai-llmgateway`](https://www.npmjs.com/package/@tanstack/ai-llmgateway) adapter.
## API entry points [#api-entry-points]
* [OpenAPI specification](https://llmgateway.io/openapi.json) — typed request, response, and error schemas
* [Authentication](https://docs.llmgateway.io/features/api-keys) — API keys and access control
* [Developer dashboard](https://llmgateway.io/dashboard) — projects, keys, usage, and budgets
* [API versioning and deprecation policy](https://docs.llmgateway.io/resources/api-versioning) — compatibility and retirement notices
## Guides [#guides]
* [**DevPass Usage API**](https://docs.llmgateway.io/developers/devpass-usage) — Show monthly and weekly DevPass allowance meters in your application
* [**LLM Gateway CLI**](https://docs.llmgateway.io/developers/cli) — Launch coding agents, scaffold projects from templates, generate agent configs, and manage keys, budgets, and usage from the terminal
* [**Model Context Protocol (MCP)**](https://docs.llmgateway.io/developers/mcp) — Use LLM Gateway as an MCP server from Claude Code, Cursor, and other MCP clients
* [**Using the AI SDK**](https://docs.llmgateway.io/developers/ai-sdk) — Install the provider, generate and stream text, call tools, and wire up Next.js routes
* [**Image Generation with the AI SDK**](https://docs.llmgateway.io/developers/ai-sdk-images) — Generate images with `generateImage` and stream image output through chat
* [**Using TanStack AI**](https://docs.llmgateway.io/developers/tanstack-ai) — Stream chat with `useChat`, call tools, and surface reasoning through the first-party `@tanstack/ai-llmgateway` adapter
## Why the AI SDK [#why-the-ai-sdk]
The AI SDK gives you one TypeScript interface for text generation, streaming, tool calling, and image generation. Combined with LLM Gateway, a single provider instance and one API key reach every model in the catalog — see the [models page](https://llmgateway.io/models) for what's available.
## Other ways to integrate [#other-ways-to-integrate]
If you're not using the AI SDK:
* Use any OpenAI-compatible SDK against `https://api.llmgateway.io/v1` — see the [Quickstart](https://docs.llmgateway.io/quick-start)
* Use the Anthropic SDK against the [Anthropic-compatible endpoint](https://docs.llmgateway.io/features/anthropic-endpoint)
* Call the REST API directly — see the API reference in the sidebar
## Brand assets [#brand-assets]
For integration listings, presentations, and partner pages, download the
official [LLM Gateway brand assets](https://llmgateway.io/brand) in SVG or transparent PNG.
The brand guide covers the horizontal logo, standalone symbol, clear space,
minimum sizes, background colors, and typography.
# LLM Gateway MCP Server
URL: https://llmgateway.io/guides/mcp
Connect your AI assistant to LLM Gateway to inspect your usage and costs, discover your most-used models, providers and coding apps, and generate text or images. The same API key connects all of these tools.
**Using DevPass?** This integration also works with a
[DevPass](https://devpass.llmgateway.io) plan key. Use canonical model IDs
without a provider prefix (`model-id` instead of `provider/model-id`) —
provider-pinned routing is not available on coding plans; the gateway picks
the provider for you.
## Video walkthrough [#video-walkthrough]
## Connection and discovery [#connection-and-discovery]
Connect with Streamable HTTP at `https://llmgateway.io/mcp` or `https://api.llmgateway.io/mcp`. Send an API key in `Authorization: Bearer `. A GET without an SSE Accept header returns public server information; browser navigation on the main domain opens the setup page.
For protocol requests, POST a single JSON-RPC message with `Content-Type: application/json` and `Accept: application/json, text/event-stream`. Initialize first, then send the negotiated `MCP-Protocol-Version` header on subsequent requests. The transport is stateless: requests return JSON, accepted notifications return an empty 202, and standalone SSE subscriptions and session deletion return 405. Clients using the original HTTP+SSE bridge remain supported.
[Protected resource metadata](https://api.llmgateway.io/.well-known/oauth-protected-resource/mcp) publishes authentication discovery. Unauthorized protocol requests include a `WWW-Authenticate` challenge pointing to it.
## What is MCP? [#what-is-mcp]
The Model Context Protocol (MCP) is an open standard that allows AI assistants to connect with external tools and data sources. LLM Gateway's MCP server exposes tools for:
* **Account and usage analytics** - Check spending limits, request/token totals, costs, trends, and provider/model/app rankings
* **Chat completions** - Send messages to any supported LLM
* **Image generation** - Generate images using models like Qwen Image
* **Nano Banana image generation** - Generate images with Gemini 3 Pro Image and optionally save to disk
* **Model discovery** - List available models with capabilities and pricing
## Available Tools [#available-tools]
### `get-account` [#get-account]
Inspect the connected user, organization, project, role, analytics scope, and API key spending limits. Owners and admins also receive the organization's current credit balance. The balance is not a DevPass plan allowance. No parameters are required, and credentials are never returned.
### `get-usage` [#get-usage]
Get request/token totals, errors, cache hits, costs, a time series, and your most-used provider, model, and coding agent/app **by request count**.
| Parameter | Description |
| ------------- | ---------------------------------------------------------------------------------- |
| `from` | Optional first UTC date, `YYYY-MM-DD`, inclusive. Defaults to 29 days before `to`. |
| `to` | Optional last UTC date, inclusive. Defaults to today. Maximum range: 366 days. |
| `granularity` | `day` (default) or `hour`. Hourly reports allow at most 31 days. |
```json
{
"from": "2026-08-01",
"to": "2026-08-31",
"granularity": "day"
}
```
The response includes `scope`, the resolved dates, `updatedAt`, `totals`, `series`, `mostUsedProvider`, `mostUsedModel`, `mostUsedApp`, and `appUsageCoverage`. Only time buckets with activity appear in `series`. Empty periods return zero totals, an empty series, and null rankings.
### `get-usage-breakdown` [#get-usage-breakdown]
Rank providers, models, coding apps, or API keys by requests, inference cost, or tokens.
| Parameter | Description |
| ------------ | ----------------------------------------------------------------------- |
| `group_by` | Required: `provider`, `model`, `app`, or `api_key`. |
| `sort_by` | `requests` (default), `cost`, or `tokens`, descending. Ties use the ID. |
| `from`, `to` | Same inclusive UTC dates as `get-usage`. |
| `limit` | Results per page: 1–100, default 10. |
| `offset` | Results to skip: 0–10000, default 0. |
```json
{
"group_by": "app",
"sort_by": "cost",
"limit": 10
}
```
The response includes each row's ID, display name, requests, tokens and costs, plus `pagination.hasMore` and `coverage`. Increase `offset` by `limit` to get the next page. Known app aliases are combined before ranking. `unknown` identifies requests with no recorded source; custom app names remain as recorded.
### Analytics scope and cost fields [#analytics-scope-and-cost-fields]
* Owners and admins see the **connected project's** usage across its keys. Developers see only the keys they created in that project, including inactive-key history.
* A project API key does not grant access to another project or organization. Tools do not accept scope overrides. Use a key for the project you want to inspect.
* Use an active user API key. Customer credentials and expired or revoked keys cannot read account analytics. Project access is checked on every analytics request.
* Analytics tools are read-only, incur no model charges, and remain available when a key or member reaches a spending limit. Generation tools still enforce those limits.
* `costUsd` is inference usage cost. `creditsCostUsd` and `byokCostUsd` separate gateway credits from provider costs paid with your own provider keys. `dataStorageCostUsd` is separate. These are usage statistics, not invoice totals or exact changes in credit balance.
* Statistics come from hourly aggregates, survive request-retention cleanup, and may lag recent requests. `updatedAt` reports the last summary aggregation in the requested period.
* App attribution uses the request's recorded source, including recognized coding clients and `x-source` values. MCP generation calls preserve client attribution headers. Configure `x-source` on your MCP connection if your client does not identify itself. Attribution does not identify which person used a shared key.
* Historical per-key app statistics start when per-key source aggregation is enabled. `appUsageCoverage` / `coverage` compare recorded breakdown requests with total requests; `complete: false` means rankings cover only part of the period. This differs from an `unknown` source, which is a recorded request without app attribution.
All three tools return JSON in both `structuredContent` and a text content block for older clients. An unavailable backend produces a tool error, never a fabricated zero-usage report.
### `chat` [#chat]
Send a message to any LLM and get a response.
**Parameters:**
* `model` (string) - A model ID from `list-models` or the [live catalog](https://llmgateway.io/models)
* `messages` (array) - Array of messages with `role` and `content`
* `temperature` (number, optional) - Sampling temperature (0-2)
* `max_tokens` (number, optional) - Maximum tokens to generate
**Example:**
```json
{
"model": "MODEL_ID",
"messages": [{ "role": "user", "content": "Explain quantum computing" }],
"temperature": 0.7
}
```
### `generate-image` [#generate-image]
Generate images from text prompts using AI image models.
**Parameters:**
* `prompt` (string) - Text description of the image to generate
* `model` (string, optional) - Image model (default: `"qwen-image-3.0"`)
* `size` (string, optional) - Image size (default: `"1024x1024"`)
* `n` (number, optional) - Number of images (1-4, default: 1)
**Example:**
```json
{
"prompt": "A serene mountain landscape at sunset",
"model": "qwen-image-3.0-pro",
"size": "1024x1024"
}
```
### `generate-nano-banana` [#generate-nano-banana]
Generate an image using Gemini 3 Pro Image ("Nano Banana Pro"). Returns an inline image preview, and optionally saves the image to disk when the server is configured with an upload directory.
**Parameters:**
* `prompt` (string) - Text description of the image to generate
* `filename` (string, optional) - Filename for the saved image, no path separators allowed (default: `nano-banana-{timestamp}.png`)
* `aspect_ratio` (string, optional) - Aspect ratio: `"1:1"`, `"16:9"`, `"4:3"`, or `"5:4"`
**Example:**
```json
{
"prompt": "A pixel-art cat sitting on a rainbow",
"filename": "hero-image.png",
"aspect_ratio": "16:9"
}
```
**Saving images to disk** requires the `UPLOAD_DIR` environment variable to be
set on the MCP server. When set, images are saved to that directory. Without
it, images are returned inline only — no files are written to disk. See
[Enabling local image saving](#enabling-local-image-saving) for setup
instructions.
### `list-models` [#list-models]
List available LLM models with capabilities and pricing.
**Parameters:**
* `include_deactivated` (boolean, optional) - Include deactivated models
* `exclude_deprecated` (boolean, optional) - Exclude deprecated models
* `limit` (number, optional) - Maximum models to return (default: 20)
* `family` (string, optional) - Filter by model family
### `list-image-models` [#list-image-models]
List all available image generation models.
Use the tool for current model IDs, capabilities, and pricing, or browse the [live models page](https://llmgateway.io/models).
## Setup [#setup]
### Get Your API Key [#get-your-api-key]
1. Log in to your [LLM Gateway dashboard](https://llmgateway.io/dashboard)
2. Navigate to **API Keys** section
3. Create a new API key and copy it
### Configure Claude Code [#configure-claude-code]
Run the following command in your terminal:
```bash
claude mcp add --transport http --scope user llmgateway https://api.llmgateway.io/mcp \
--header "Authorization: Bearer your-api-key-here"
```
**Alternative: Manual configuration**
You can also add the MCP server manually by editing `~/.claude.json` (user scope) or `.mcp.json` in your project root (project scope):
```json
{
"mcpServers": {
"llmgateway": {
"url": "https://api.llmgateway.io/mcp",
"headers": {
"Authorization": "Bearer your-api-key-here"
}
}
}
}
```
Restart Claude Code after manual configuration changes.
### Test the Integration [#test-the-integration]
Try using the tools in Claude Code:
* "Show my usage and costs for the last 30 days"
* "Generate an image of a futuristic city using the generate-image tool"
* "Use generate-nano-banana to create a hero image for my landing page"
* "Which model, provider, and coding app do I use most?"
### Get Your API Key [#get-your-api-key-1]
1. Log in to your [LLM Gateway dashboard](https://llmgateway.io/dashboard)
2. Navigate to **API Keys** section
3. Create a new API key and copy it
4. Set it as an environment variable: `export LLM_GATEWAY_API_KEY="your-api-key-here"`
### Configure Codex [#configure-codex]
Run the following command in your terminal:
```bash
codex mcp add llmgateway --url https://api.llmgateway.io/mcp \
--bearer-token-env-var LLM_GATEWAY_API_KEY
```
**Alternative: Manual configuration**
You can also add the MCP server manually by editing `~/.codex/config.toml`:
```toml
[mcp_servers.llmgateway]
url = "https://api.llmgateway.io/mcp"
bearer_token_env_var = "LLM_GATEWAY_API_KEY"
```
### Test the Integration [#test-the-integration-1]
Run `/mcp` in the Codex TUI to confirm the `llmgateway` server is connected. Try:
* "Show my usage and costs for the last 30 days"
* "Generate an image of a futuristic city using the generate-image tool"
* "Use generate-nano-banana to create a hero image for my landing page"
* "Which model, provider, and coding app do I use most?"
### Get Your API Key [#get-your-api-key-2]
1. Log in to your [LLM Gateway dashboard](https://llmgateway.io/dashboard)
2. Navigate to **API Keys** section
3. Create a new API key and copy it
### Configure Cursor [#configure-cursor]
Add the following to your Cursor MCP configuration file (`~/.cursor/mcp.json`):
```json
{
"mcpServers": {
"llmgateway": {
"url": "https://api.llmgateway.io/mcp",
"headers": {
"Authorization": "Bearer your-api-key-here"
}
}
}
}
```
Or open the Command Palette (`Cmd/Ctrl + Shift + P`), search for **"Cursor Settings"**, then go to **Tools & Integrations** > **Add Custom MCP** and paste the configuration above.
Cursor v0.48.0+ is required for Streamable HTTP MCP support.
### Test the Integration [#test-the-integration-2]
Open a chat in **Agent Mode**, click the **Select Tools** icon, and verify the LLM Gateway tools appear. Try:
* "Show my usage and costs for the last 30 days"
* "Generate an image of a futuristic city using the generate-image tool"
* "Use generate-nano-banana to create a hero image for my landing page"
* "Which model, provider, and coding app do I use most?"
LLM Gateway's MCP server supports the standard HTTP Streamable transport. Configure your client with:
* **Endpoint:** `https://api.llmgateway.io/mcp`
* **Authentication:** Bearer token via `Authorization` header or `x-api-key` header
* **Protocol Version:** 2024-11-05
**Direct HTTP Example:**
```bash
curl -X POST https://api.llmgateway.io/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'
```
**Server-Sent Events (SSE):**
For real-time updates, connect with `Accept: text/event-stream`:
```bash
curl -N https://api.llmgateway.io/mcp \
-H "Accept: text/event-stream" \
-H "Authorization: Bearer your-api-key"
```
## Verify with MCP Inspector [#verify-with-mcp-inspector]
For a standalone connection check, start the official [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector):
```bash
pnpm dlx @modelcontextprotocol/inspector
```
In Inspector, add a server manually:
1. Choose **Streamable HTTP** and enter `https://api.llmgateway.io/mcp`.
2. In the server settings, add a custom `Authorization` header with `Bearer YOUR_API_KEY`.
3. Connect, open **Tools**, and select **chat**.
4. Enter an accessible text model ID from the [live catalogue](https://llmgateway.io/models), a `messages` array, and an optional `max_tokens` limit.
5. Click **Execute Tool** and inspect the returned text and token usage.
For example, the `messages` field accepts:
```json
[
{
"role": "user",
"content": "In one sentence, explain what an API gateway does."
}
]
```
Keep `stream` disabled: this MCP tool returns a complete response. Model generation is billed to the workspace that issued the key. Use **list-models** to discover available model metadata without generating a response.
Use HTTPS for the hosted endpoint. A self-hosted gateway also needs an HTTPS `MCP_GATEWAY_URL` for authenticated generation calls.
## Use Cases [#use-cases]
### Usage and Spending [#usage-and-spending]
```text
What did I spend this month, and which coding app accounts for the most cost?
Show my most-used model and provider, then compare daily usage with last month.
```
Use `get-account` to confirm the scope, `get-usage` for each period, and `get-usage-breakdown` with `group_by: "app"` and `sort_by: "cost"` for the app ranking.
### Multi-Model Access in Claude Code [#multi-model-access-in-claude-code]
Use Claude Code to interact with models it doesn't natively support:
```
List available models, then use the chat tool with a suitable model to review this code.
```
### Image Generation [#image-generation]
Generate images directly from your AI assistant:
```
Use generate-image to create a logo for my new startup.
It should be minimalist, blue and white, representing AI and cloud computing.
```
### Nano Banana (Gemini Image Generation) [#nano-banana-gemini-image-generation]
Generate images with Gemini 3 Pro for use in your project:
```
Use generate-nano-banana to create a hero image for my landing page with a 16:9 aspect ratio.
```
### Cost-Effective Model Selection [#cost-effective-model-selection]
Query available models to find the best option for your task:
```
List models and their pricing, then choose a suitable low-cost model for this task.
```
## Authentication [#authentication]
The MCP server supports two authentication methods:
1. **Bearer Token** - `Authorization: Bearer your-api-key`
2. **API Key Header** - `x-api-key: your-api-key`
Use the same project API key you use for inference. Analytics access follows the [scope rules above](#analytics-scope-and-cost-fields).
## OAuth Support [#oauth-support]
For applications that prefer OAuth authentication, LLM Gateway's MCP server implements OAuth 2.0:
* **Authorization Endpoint:** `/oauth/authorize`
* **Token Endpoint:** `/oauth/token`
* **Registration Endpoint:** `/oauth/register`
* **Supported Flows:** Authorization Code, Client Credentials
## Enabling Local Image Saving [#enabling-local-image-saving]
By default, `generate-nano-banana` returns images inline without writing to disk. To enable saving generated images to the server filesystem, the `UPLOAD_DIR` environment variable must be set on the **gateway host** at startup. This is a server-side setting — it cannot be configured from the client.
This is only possible for **self-hosted** MCP deployments. Configure `UPLOAD_DIR` using your deployment method:
* **Docker:** Pass `-e UPLOAD_DIR=/data/images` or add it to your `docker-compose.yml` environment section.
* **systemd:** Add `Environment=UPLOAD_DIR=/data/images` to your service unit file.
* **.env file:** Add `UPLOAD_DIR=/data/images` to the `.env` file loaded by your gateway process.
The shared hosted endpoint (`api.llmgateway.io`) does not support configuring
`UPLOAD_DIR`. On the hosted service, images are always returned inline — no
files are written to disk. To enable server-side image saving, you must
self-host the MCP server and set `UPLOAD_DIR` at startup.
## Troubleshooting [#troubleshooting]
### Connection Errors [#connection-errors]
If you're having trouble connecting:
1. Verify your API key is valid
2. Check the endpoint URL is correct: `https://api.llmgateway.io/mcp`
3. Ensure your firewall allows outbound HTTPS connections
### Tool Not Found [#tool-not-found]
If tools aren't appearing:
1. Restart your MCP client
2. Check the configuration syntax
3. Verify the MCP server is responding: `GET https://api.llmgateway.io/mcp`
### Rate Limiting [#rate-limiting]
The MCP server respects your account's rate limits. If you're hitting limits:
1. Check your usage in the dashboard
2. Consider upgrading your plan
3. Implement request queuing in your application
Need help? Join our [Discord community](https://llmgateway.io/discord) for
support.
## Benefits [#benefits]
* **Unified Access** - Discover the [live model and provider catalog](https://llmgateway.io/models) through one interface
* **Cost Tracking** - Ask your assistant about usage, spending, and your most-used models, providers, and apps
* **Caching** - Automatic response caching reduces costs and latency
* **Fallback** - Automatic provider failover ensures reliability
* **Image Generation** - Generate images directly from your AI assistant
# Using TanStack AI
URL: https://docs.llmgateway.io/developers/tanstack-ai
[TanStack AI](https://tanstack.com/ai) ships a first-party LLM Gateway adapter: [`@tanstack/ai-llmgateway`](https://www.npmjs.com/package/@tanstack/ai-llmgateway), maintained in the TanStack AI repository alongside the OpenAI and Anthropic adapters. One adapter and one API key reach every model in the catalog.
## Install [#install]
```bash
pnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-llmgateway
```
`@tanstack/ai-react` is the React client; TanStack AI also ships Vue, Svelte, Angular, and Preact packages that work with the same adapter.
Set your API key (create one from the [dashboard](https://llmgateway.io/dashboard)):
```bash
export LLM_GATEWAY_API_KEY=llmgtwy_your_key_here
```
## Stream chat from a server route [#stream-chat-from-a-server-route]
`llmGatewayText(model)` creates the adapter and reads the key from `LLM_GATEWAY_API_KEY`:
```typescript
// app/api/chat/route.ts
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { llmGatewayText } from "@tanstack/ai-llmgateway";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: llmGatewayText("gpt-5.6-terra"),
messages,
});
return toServerSentEventsResponse(stream);
}
```
Switching models is a one-line change — the same adapter serves every model:
```typescript
adapter: llmGatewayText("claude-sonnet-5"),
```
## Model ID formats [#model-id-formats]
LLM Gateway supports two model ID formats:
* **Canonical model IDs** (`gpt-5.6-terra`) — smart routing picks the best provider based on uptime, throughput, price, and latency
* **Provider-prefixed IDs** (`moonshot/kimi-k3`) — routes to a specific provider with automatic failover if uptime drops below 90%
A curated set of flagship models carries typed metadata (`LLMGATEWAY_CHAT_MODELS`) with editor autocomplete for input modalities and options; any other ID from the [models page](https://llmgateway.io/models) still works. See the [routing documentation](https://docs.llmgateway.io/features/routing) for details.
## Connect the React client [#connect-the-react-client]
`useChat` consumes the AG-UI event stream from the route above — no client-side API key, no per-provider wiring:
```tsx
// components/chat.tsx
"use client";
import { fetchServerSentEvents, useChat } from "@tanstack/ai-react";
import { useState } from "react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
return (
);
}
```
## Tool calling [#tool-calling]
Define tools with `toolDefinition` and attach a server handler — TanStack AI runs the tool loop for you:
```typescript
import { chat, toServerSentEventsResponse, toolDefinition } from "@tanstack/ai";
import { llmGatewayText } from "@tanstack/ai-llmgateway";
import { z } from "zod";
const getWeather = toolDefinition({
name: "get_weather",
description: "Get the current weather for a location",
inputSchema: z.object({
location: z.string(),
}),
}).server(async ({ location }) => {
return { temperature: 72, condition: "sunny" };
});
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: llmGatewayText("gpt-5.6-terra"),
messages,
tools: [getWeather],
});
return toServerSentEventsResponse(stream);
}
```
## Reasoning models [#reasoning-models]
Reasoning models stream their thinking as `reasoning_content` deltas, which the adapter surfaces as AG-UI `REASONING_*` events — they arrive in `useChat` as `thinking` parts. Control the depth with `reasoning_effort` in `modelOptions`:
```typescript
const stream = chat({
adapter: llmGatewayText("kimi-k3"),
messages,
modelOptions: {
temperature: 0.7,
reasoning_effort: "high",
},
});
```
`reasoning_effort` accepts the extended scale `none` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`; which tiers a model honors depends on the model and the provider it routes to. Parameters a routed provider doesn't support are stripped server-side, so `modelOptions` stay portable across models. See [reasoning support](https://docs.llmgateway.io/features/reasoning).
## Summarization [#summarization]
The adapter also covers TanStack AI's `summarize` surface:
```typescript
import { summarize } from "@tanstack/ai";
import { llmGatewaySummarize } from "@tanstack/ai-llmgateway";
const result = await summarize({
adapter: llmGatewaySummarize("gpt-5.4-mini"),
text: "Long article text...",
stream: false,
});
```
## Self-hosted deployments [#self-hosted-deployments]
`createLLMGatewayText` takes the key explicitly plus a `baseURL` for self-hosted gateways:
```typescript
import { createLLMGatewayText } from "@tanstack/ai-llmgateway";
const adapter = createLLMGatewayText(
"gpt-5.6-terra",
process.env.LLM_GATEWAY_API_KEY!,
{
baseURL: "https://gateway.internal.example.com/v1",
},
);
```
Every request made through TanStack AI shows up in your
[Activity](https://docs.llmgateway.io/learn/activity) and [Usage & Metrics](https://docs.llmgateway.io/learn/usage-metrics)
dashboards like any other gateway request — with per-request cost, tokens, and
latency.
## Next steps [#next-steps]
* [TanStack AI adapter reference](https://tanstack.com/ai/latest/docs/adapters/llmgateway)
* [Routing and fallback](https://docs.llmgateway.io/features/routing)
* [Reasoning support](https://docs.llmgateway.io/features/reasoning) and [caching](https://docs.llmgateway.io/features/caching)
# AWS Bedrock Integration
URL: https://docs.llmgateway.io/integrations/aws-bedrock
AWS Bedrock is Amazon's fully managed service that provides access to foundation models from leading AI companies. This guide shows how to create AWS Bedrock Long-Term API Keys and integrate them with LLM Gateway.
## Prerequisites [#prerequisites]
* An AWS account with Bedrock access enabled
* LLM Gateway account or self-hosted instance
## Overview [#overview]
AWS Bedrock supports **Long-Term API Keys** for simplified authentication. These keys provide direct API access without requiring IAM credentials or complex authentication flows.
## Create AWS Bedrock Long-Term API Key [#create-aws-bedrock-long-term-api-key]
### Enable Model Access in Bedrock [#enable-model-access-in-bedrock]
1. Log into the **AWS Console**
2. Navigate to **AWS Bedrock** service
3. Go to **Model access** in the left sidebar
4. Click **Manage model access**
5. Enable the models you want to use (e.g., Claude 3.5, Llama 3)
6. Wait for access to be granted (usually instant for most models)
### Create Long-Term API Key [#create-long-term-api-key]
1. In AWS Bedrock console, navigate to **API Keys** in the left sidebar
2. Click **Create Long-Term API Key**
3. Set expiry date ("Never expires" is recommended)
4. Click **Generate**
5. **Important**: Copy the API key immediately - it's only shown once!
## Add to LLM Gateway [#add-to-llm-gateway]
### Navigate to Provider Keys [#navigate-to-provider-keys]
1. Log into [LLM Gateway Dashboard](https://llmgateway.io/dashboard)
2. Select your organization and project
3. Go to **Provider Keys** in the sidebar
### Add AWS Bedrock Provider Key [#add-aws-bedrock-provider-key]
1. Click **Add** for **AWS Bedrock**
2. Paste your Long-Term API Key
3. **Select Region Prefix** based on where you want to use your models:
* **us.** - For US regions (`us-east-1`, `us-west-2`)
* **eu.** - For European regions (`eu-central-1`, `eu-west-1`)
* **global.** - For global/cross-region endpoints
4. Click **Add Key**
The system will validate your key and confirm the connection.
### Test the Integration [#test-the-integration]
Test your integration with a simple API call:
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Authorization: Bearer YOUR_LLMGATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "aws-bedrock/claude-3-5-sonnet",
"messages": [
{
"role": "user",
"content": "Hello from AWS Bedrock!"
}
]
}'
```
Replace `YOUR_LLMGATEWAY_API_KEY` with your LLM Gateway API key.
## Available Models [#available-models]
Once configured, you can access all AWS Bedrock models through LLM Gateway:
* **Anthropic Claude**: `aws-bedrock/claude-3-5-sonnet`, `aws-bedrock/claude-3-5-haiku`
* **Meta Llama**: `aws-bedrock/llama-3-2-90b`, `aws-bedrock/llama-3-2-11b`
* **Amazon Titan**: `aws-bedrock/amazon.titan-text-express-v1`
* **And more...**
Browse all available models at [llmgateway.io/models](https://llmgateway.io/models?provider=aws-bedrock)
## Troubleshooting [#troubleshooting]
### "Model not available" error [#model-not-available-error]
* Verify you've enabled model access in AWS Bedrock console
* Check that the region where you created your key has access to the model
* Some models are only available in specific regions
### Rate limiting [#rate-limiting]
* AWS Bedrock has request quotas per model and region
* Monitor usage in AWS Bedrock console
* Consider requesting quota increases for high-volume workloads
# Azure Integration
URL: https://docs.llmgateway.io/integrations/azure
Azure provides access to OpenAI's powerful language models through Microsoft's enterprise cloud infrastructure. This guide shows how to create an Azure resource, deploy models, and integrate them with LLM Gateway.
Azure exposes three separate providers in LLM Gateway: `azure` for OpenAI
models, `azure-ai-foundry` for third-party models served through the Azure
Models inference endpoint, and `azure-anthropic` for Claude models on
Microsoft Foundry. [Open an
issue](https://github.com/theopenco/llmgateway/issues/new) to request support
for other model types.
## Prerequisites [#prerequisites]
* An Azure account with an active subscription
* LLM Gateway account or self-hosted instance
## Overview [#overview]
Azure provides enterprise-grade access to OpenAI models with enhanced security, compliance, and regional availability. LLM Gateway integrates seamlessly with Azure deployments.
## Create Azure Resource [#create-azure-resource]
### Create an Azure OpenAI Resource [#create-an-azure-openai-resource]
1. Log into the **Azure Portal** ([https://portal.azure.com](https://portal.azure.com))
2. Click **Create a resource**
3. Search for **Azure OpenAI** and select it
4. Click **Create**
5. Configure the resource:
* **Subscription**: Select your Azure subscription
* **Resource group**: Create new or select existing
* **Region**: Choose a region (e.g., East US, West Europe)
* **Name**: Enter a unique resource name (this will be your ``)
* **Pricing tier**: Select Standard S0
6. Click **Review + create**, then **Create**
7. Wait for deployment to complete
**Important**: Note your resource name - it will be used in the base URL: `https://.openai.azure.com`
### Deploy Models [#deploy-models]
1. Navigate to your Azure resource in the Azure Portal
2. Click **Go to Azure OpenAI Studio** or visit [https://oai.azure.com](https://oai.azure.com)
3. In Azure Studio, select **Deployments** from the left sidebar
4. Click **Create new deployment**
5. Configure your deployment:
* **Model**: Select a model listed for Azure on the [models page](https://llmgateway.io/models?provider=azure)
* **Deployment name**: Enter a name (this must match the model identifier you'll use – use the pre-filled name)
* **Model version**: Select the latest version
* **Deployment type**: Global Standard
6. Click **Create**
7. Repeat for additional models you want to use
### Get API Key [#get-api-key]
1. In the Azure Portal, go to your Azure resource
2. Click **Keys and Endpoint** in the left sidebar
3. Copy **Key 1** or **Key 2**
4. Note your **Endpoint** URL (should be `https://.openai.azure.com`)
**Important**: Keep your API key secure - it provides access to your Azure deployments.
## Add to LLM Gateway [#add-to-llm-gateway]
### Navigate to Provider Keys [#navigate-to-provider-keys]
1. Log into [LLM Gateway Dashboard](https://llmgateway.io/dashboard)
2. Select your organization and project
3. Go to **Provider Keys** in the sidebar
### Add Azure Provider Key [#add-azure-provider-key]
1. Click **Add** for **Azure**
2. Enter your **API Key** from Azure Portal
3. Enter your **Resource Name** (the name from your Azure endpoint URL)
* Example: If your endpoint is `https://my-openai-resource.openai.azure.com`, enter `my-openai-resource`
4. Select your preferred **type** (Azure OpenAI or AI Foundry)
5. Adapt the **Validation Model** to a model that you already deployed and is available
This is a one time check to ensure the API key is valid and the model can be accessed.
6. Click **Add Key**
The system will validate your key and confirm the connection.
### Test the Integration [#test-the-integration]
Test your integration with a simple API call:
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Authorization: Bearer YOUR_LLMGATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "azure/gpt-4o-mini",
"messages": [
{
"role": "user",
"content": "Hello from Azure!"
}
]
}'
```
Replace `YOUR_LLMGATEWAY_API_KEY` with your LLM Gateway API key.
## Available Models [#available-models]
Browse the [Azure models page](https://llmgateway.io/models?provider=azure) for currently available mappings. Only models deployed in your Azure resource can serve requests. Ensure each deployment name matches the model identifier you send to LLM Gateway.
## Priority processing [#priority-processing]
Azure sells a low-latency **Priority** processing tier, requested per request via
the OpenAI-compatible `service_tier` field:
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Authorization: Bearer YOUR_LLMGATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "azure/gpt-5.1",
"service_tier": "priority",
"messages": [
{
"role": "user",
"content": "Hello from Azure!"
}
]
}'
```
Requirements:
* A **Global Standard** or **Data Zone (US)** deployment. Regional standard and
EU data zone deployments are not supported. Not every priority-capable model is
offered on both — some are Global Standard only, so check Microsoft's
[availability tables](https://learn.microsoft.com/azure/foundry/openai/concepts/priority-processing)
for the model you deploy.
* Model version `2025-12-01` or later, on a model that offers the tier — see the
provider card on the [models page](https://llmgateway.io/models?provider=azure).
* A subscription entitled to priority processing.
Both provider key types work — the v1 (AI Foundry) and the legacy deployment-based
surfaces each accept `service_tier` and report the tier they served.
Azure serves a priority request at standard when the entitlement is missing,
during peak load, on ramp-rate limits, and for long-context prompts on some
models. It reports the tier it actually served, so a downgraded request is
billed at standard rates and `used_service_tier` comes back `null`. See
[service tiers](https://docs.llmgateway.io/features/service-tiers) for the full behaviour.
## Claude models on Microsoft Foundry [#claude-models-on-microsoft-foundry]
Claude models are served by Microsoft Foundry through Anthropic's Messages API rather than an OpenAI-compatible endpoint, so they use their own `azure-anthropic` provider in LLM Gateway.
1. In the Foundry portal, deploy the Claude model you want. The deployment name is what you pass as the model at inference time, so keep the default (the model ID) unless you have a reason to rename it.
2. Add an **Azure Anthropic** provider key in LLM Gateway with your Foundry API key and the resource name from `https://.services.ai.azure.com`.
3. Call it with the `azure-anthropic/` model string:
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Authorization: Bearer YOUR_LLMGATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "azure-anthropic/claude-opus-4-8",
"messages": [
{
"role": "user",
"content": "Hello from Microsoft Foundry!"
}
]
}'
```
If a request returns `DeploymentNotFound`, the model has not been deployed in that Foundry resource, or the deployment name does not match the model string you sent. Browse the Claude models routed through this provider at [llmgateway.io/models](https://llmgateway.io/models?provider=azure-anthropic).
## Troubleshooting [#troubleshooting]
### "Deployment not found" error [#deployment-not-found-error]
* Verify you've created a deployment in Azure Studio
* Ensure the deployment name exactly matches the model name you're requesting
* Check that the deployment is in the same resource as your API key
### "Resource not found" error [#resource-not-found-error]
* Verify the resource name is correct (check your Azure Portal endpoint URL)
* Ensure your API key belongs to the correct Azure resource
* Confirm the resource is in an active state in Azure Portal
### Rate limiting [#rate-limiting]
* Azure has Tokens Per Minute (TPM) quotas per deployment
* Monitor usage in Azure Studio under **Quotas**
* Request quota increases through Azure Portal if needed for high-volume workloads
### Region availability [#region-availability]
* Not all models are available in all Azure regions
* Check [Azure model availability](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#model-summary-table-and-region-availability) for your region
* Consider creating resources in multiple regions for better availability
# Vertex AI Anthropic Integration
URL: https://docs.llmgateway.io/integrations/vertex-anthropic
Run Claude models (Sonnet, Opus, Haiku) on Google Cloud Vertex AI through LLM Gateway. This guide shows how to set up a GCP service account and integrate it with LLM Gateway using automatic OAuth2 token management — no manual token rotation required.
## Prerequisites [#prerequisites]
* A Google Cloud project with billing enabled
* LLM Gateway account or self-hosted instance
## Set up Google Cloud [#set-up-google-cloud]
### Enable the Vertex AI API [#enable-the-vertex-ai-api]
In the [Google Cloud Console](https://console.cloud.google.com/apis/library/aiplatform.googleapis.com), enable the **Vertex AI API** for your project.
### Enable Claude Models in Model Garden [#enable-claude-models-in-model-garden]
Navigate to **Vertex AI > Model Garden** in the Cloud Console. Search for the Claude models you want to use and click **Enable** on each one.
Available models:
* `claude-sonnet-4-6`
* `claude-sonnet-4-5`
* `claude-haiku-4-5`
* `claude-opus-4-5`
* `claude-opus-4-6`
* `claude-opus-4-7`
### Create a Service Account [#create-a-service-account]
Create a service account with the required permissions:
```bash
# Create the service account
gcloud iam service-accounts create vertex-ai-caller \
--display-name="Vertex AI Caller" \
--project=YOUR_PROJECT_ID
# Grant the Vertex AI User role
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:vertex-ai-caller@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"
```
### Download the Service Account Key [#download-the-service-account-key]
```bash
gcloud iam service-accounts keys create service-account.json \
--iam-account=vertex-ai-caller@YOUR_PROJECT_ID.iam.gserviceaccount.com
```
Then convert it to a single-line string:
```bash
cat service-account.json | tr -d '\n'
```
Keep the output handy — you'll paste it into LLM Gateway in the next steps.
## Add to LLM Gateway [#add-to-llm-gateway]
### Navigate to Provider Keys [#navigate-to-provider-keys]
1. Log into [LLM Gateway Dashboard](https://llmgateway.io/dashboard)
2. Select your organization and project
3. Go to **Provider Keys** in the sidebar
### Add Vertex Anthropic Provider Key [#add-vertex-anthropic-provider-key]
1. Click **Add** for **Vertex AI (Anthropic)**
2. Paste the single-line service account JSON as the **API Key**
3. Leave **Region** empty to use the recommended `global` endpoint, or set a specific region (e.g. `us-east5`) if you need data residency
4. Click **Add Key**
The project ID is extracted automatically from the service account JSON — no separate project field is needed.
### Test the Integration [#test-the-integration]
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Authorization: Bearer YOUR_LLMGATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "vertex-anthropic/claude-sonnet-4-6",
"messages": [
{
"role": "user",
"content": "Hello from Vertex Anthropic!"
}
]
}'
```
Replace `YOUR_LLMGATEWAY_API_KEY` with your LLM Gateway API key.
## Self-Host Configuration [#self-host-configuration]
If you're self-hosting LLM Gateway, configure the provider via environment variables instead of the dashboard:
```bash
LLM_VERTEX_ANTHROPIC_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"YOUR_PROJECT_ID","private_key":"-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n","client_email":"vertex-ai-caller@YOUR_PROJECT_ID.iam.gserviceaccount.com","token_uri":"https://oauth2.googleapis.com/token"}
LLM_VERTEX_ANTHROPIC_REGION=global
```
The project ID is extracted automatically from the service account JSON, so
`LLM_VERTEX_ANTHROPIC_PROJECT` only needs setting to call a different project
than the service account's own.
## How Token Refresh Works [#how-token-refresh-works]
LLM Gateway handles the OAuth2 token lifecycle automatically:
1. On first request, the service account JSON is parsed and used to sign a JWT
2. The JWT is exchanged for an OAuth2 access token via Google's token endpoint
3. The token is cached in Redis with a **50-minute TTL** (Google tokens expire after 60 minutes)
4. An in-memory cache avoids Redis round-trips on subsequent requests
5. When the cached token expires, a new one is generated transparently
This means:
* No manual `gcloud auth print-access-token` commands
* No cron jobs to refresh tokens
* Works at any request rate (token generation happens at most once per 50 minutes)
* Multi-instance deployments share the cached token via Redis
## Available Regions [#available-regions]
LLM Gateway defaults to the **`global`** endpoint, which Anthropic recommends: requests are routed dynamically to whichever region has capacity, and there is no pricing premium.
| Region | Notes |
| ----------------- | --------------------------------------------- |
| `global` | Default — dynamic routing, no pricing premium |
| `us` | Multi-region (US only); 10% premium |
| `eu` | Multi-region (EU only); 10% premium |
| `us-east5` | Columbus, Ohio; 10% premium |
| `us-central1` | Iowa; 10% premium |
| `europe-west1` | Belgium; 10% premium |
| `europe-west4` | Netherlands; 10% premium |
| `asia-southeast1` | Singapore; 10% premium |
Regional and multi-region endpoints add a 10% pricing premium on Claude Sonnet
4.5 and newer models. They are also required if you need single-region data
residency or provisioned throughput. See [Anthropic's Vertex
docs](https://platform.claude.com/docs/en/api/claude-on-vertex-ai#global-multi-region-and-regional-endpoints)
for details.
## Available Models [#available-models]
Once configured, you can access Claude models on Vertex AI through LLM Gateway. Browse the full, up-to-date list at [llmgateway.io/models?provider=vertex-anthropic](https://llmgateway.io/models?provider=vertex-anthropic).
## Troubleshooting [#troubleshooting]
### 401 UNAUTHENTICATED / ACCESS\_TOKEN\_TYPE\_UNSUPPORTED [#401-unauthenticated--access_token_type_unsupported]
The gateway is sending an invalid token. Check:
* The service account JSON is valid and complete
* The service account has `roles/aiplatform.user` on the project
### 403 Permission Denied [#403-permission-denied]
The service account lacks permissions. Grant the `Vertex AI User` role:
```bash
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:vertex-ai-caller@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"
```
### Model Not Found [#model-not-found]
The Claude model may not be enabled in your project's Model Garden, or may not be available in the selected region. Check the [Model Garden](https://console.cloud.google.com/vertex-ai/model-garden) in Cloud Console.
# Anthropic API Compatibility
URL: https://docs.llmgateway.io/features/anthropic-endpoint
LLMGateway provides a native Anthropic-compatible endpoint at `/v1/messages` that allows you to use any model in our catalog while maintaining the familiar Anthropic API format
This is especially useful for applications designed for Claude that you want to extend to use other models.
Enjoy a 50% discount on our Anthropic models for a limited time.
## Overview [#overview]
The Anthropic endpoint transforms requests from Anthropic's message format to the OpenAI-compatible format used by LLMGateway, then transforms the responses back to Anthropic's format. This means you can:
* Use **any model** available in LLMGateway with Anthropic's API format
* Maintain existing code that uses Anthropic's SDK or API format
* Access models from OpenAI, Google, Cohere, and other providers through the Anthropic interface
* Leverage LLMGateway's routing, caching, and cost optimization features
## Basic Usage [#basic-usage]
## Configuration for Claude Code [#configuration-for-claude-code]
This endpoint is perfect for configuring Claude Code to use any model available in LLMGateway:
```bash
export ANTHROPIC_BASE_URL=https://api.llmgateway.io
export ANTHROPIC_AUTH_TOKEN=llmgtwy_your_api_key_here
# optional: specify a model, otherwise it uses the default Claude model
export ANTHROPIC_MODEL=gpt-5 # or any model from our catalog
# now run claude!
claude
```
Environment variables are read once at startup. The `/model` picker lists
Claude models only, so non-Claude models are selected with `ANTHROPIC_MODEL`
or `--model`. See the [Claude Code guide](https://docs.llmgateway.io/guides/claude-code) for the
settings-file options and gateway model discovery.
### Choosing Models [#choosing-models]
You can use any model from the [models page](https://llmgateway.io/models). Popular options for Claude Code include:
```bash
# Use OpenAI's latest model
export ANTHROPIC_MODEL=gpt-5
# Use a cost-effective alternative
export ANTHROPIC_MODEL=gpt-5-mini
# Use Google's Gemini
export ANTHROPIC_MODEL=gemini-3.1-pro-preview
# Use Anthropic's actual Claude models
export ANTHROPIC_MODEL=claude-3-5-sonnet-20241022
```
## Environment Variables [#environment-variables]
When configuring Claude Code or other Anthropic-compatible applications, you can use these environment variables:
### ANTHROPIC\_MODEL [#anthropic_model]
Specifies the main model to use for primary requests.
* **Default**: `claude-sonnet-4-20250514`
* **Example**: `export ANTHROPIC_MODEL=gpt-5`
### ANTHROPIC\_SMALL\_FAST\_MODEL [#anthropic_small_fast_model]
Specifies a smaller, faster model used for background functionality and internal operations.
* **Default**: `claude-3-5-haiku-20241022`
* **Example**: `export ANTHROPIC_SMALL_FAST_MODEL=gpt-5-nano`
```bash
# Example configuration
export ANTHROPIC_BASE_URL=https://api.llmgateway.io
export ANTHROPIC_AUTH_TOKEN=llmgtwy_your_api_key_here
export ANTHROPIC_MODEL=gpt-5
export ANTHROPIC_SMALL_FAST_MODEL=gpt-5-nano
```
## Advanced Features [#advanced-features]
### Making a manual request [#making-a-manual-request]
```bash
curl -X POST "https://api.llmgateway.io/v1/messages" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
],
"max_tokens": 100
}'
```
### Response Format [#response-format]
The endpoint returns responses in Anthropic's message format:
```json
{
"id": "msg_abc123",
"type": "message",
"role": "assistant",
"model": "gpt-5",
"content": [
{
"type": "text",
"text": "Hello! I'm doing well, thank you for asking. How can I help you today?"
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 13,
"output_tokens": 20
},
"metadata": {
"requested_model": "gpt-5",
"used_model": "gpt-5",
"used_provider": "openai"
}
}
```
`metadata` is the same gateway object `/v1/chat/completions` returns: it names the provider, model and region that served the request, and lists every attempt in `routing` when a fallback happened (see [Routing Transparency](https://docs.llmgateway.io/features/routing#routing-transparency)). On streaming responses it is attached to the `message_delta` event.
### Request Format [#request-format]
`/v1/messages` expects Anthropic Messages requests and always answers in Anthropic's format. Because the two formats share `model` and `messages`, an OpenAI Chat Completions body can reach this endpoint by accident — and since unknown parameters are ignored rather than rejected, the request succeeds and returns an Anthropic response body that OpenAI SDKs cannot read. If your client reports an empty completion here, check that it is pointed at `/v1/chat/completions`.
Unknown parameters are deliberately ignored rather than rejected, so a valid Anthropic request is never denied for carrying an extra field. As a consequence, OpenAI-only parameters (`response_format`, `stream_options`, `max_completion_tokens`, `n`, `stop`, `seed`, `frequency_penalty`, and similar) have no effect here — the model will not honour them. Use `/v1/chat/completions` if you need them.
A body that is *structurally* OpenAI is rejected by the schema, as it always has been — OpenAI-shaped `tools` (`{"type": "function", "function": {…}}`), OpenAI content parts such as `image_url`, or assistant turns with `content: null`. Those rejections now name the mismatch and point at the right endpoint instead of returning an opaque validation error:
```json
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "This endpoint implements Anthropic's Messages API, and the request body uses OpenAI Chat Completions structures (tools[0].function) that Anthropic's format has no equivalent for. Send OpenAI-format requests to /v1/chat/completions instead, or convert the body to Anthropic's Messages format."
}
}
```
Rejected requests are recorded in your logs with a `client_error` finish reason and zero cost, so a malformed client is visible in the activity feed rather than failing silently.
### Prompt Caching [#prompt-caching]
For Claude models, `cache_control` markers on `system` and message content blocks are forwarded to the provider unchanged, including the optional `ttl` (`5m` or `1h`):
```json
{
"model": "claude-sonnet-4-6",
"max_tokens": 100,
"system": [
{
"type": "text",
"text": "",
"cache_control": { "type": "ephemeral" }
}
],
"messages": [{ "role": "user", "content": "Hello!" }]
}
```
Cache usage comes back in Anthropic's native fields: `usage.cache_creation_input_tokens` (tokens written to the cache this request, billed at the write premium), `usage.cache_read_input_tokens` (tokens served from cache at the discounted rate), and `usage.cache_creation` (the per-TTL write breakdown).
A `system` message inside `messages` (as Claude Code sends on recent Claude models) stays at its position instead of being merged into the top-level `system` prompt, so the cached conversation prefix is not rewritten on every turn. Where the upstream model does not accept that role, or does not accept it at that position (it must follow a user turn and precede an assistant turn or end the conversation), it is sent in place as a user message wrapped in `` tags. If system messages interrupt a complete tool-call/result turn, the gateway moves them after all results for that turn so the results remain adjacent to the tool calls. Incomplete turns and unrelated user messages are left unchanged. Claude Code's `tool_addition` and `tool_removal` blocks are accepted and passed to the model as a text note.
Each Claude model has a minimum cacheable prompt length (it varies by model
and is exposed as `min_cacheable_tokens` on `/v1/models`). A `cache_control`
marker on a shorter prompt is accepted but silently not cached — both cache
usage fields stay `0`. See [Provider Cache
Control](https://docs.llmgateway.io/features/caching/provider-cache-control) for details.
### Web Search [#web-search]
Anthropic's server-side web search tool works on this endpoint. Pass it as usual and the response carries `server_tool_use` and `web_search_tool_result` blocks before the text that cites them, so Anthropic SDK clients surface sources:
```json
{
"model": "claude-haiku-4-5",
"max_tokens": 400,
"messages": [{ "role": "user", "content": "What shipped in Node 24?" }],
"tools": [{ "type": "web_search_20250305", "name": "web_search" }]
}
```
Replaying the assistant turn verbatim on the next request is supported: the `server_tool_use` and `web_search_tool_result` blocks are accepted and dropped, since the provider re-runs the search rather than reusing the previous results.
### Tool Search [#tool-search]
Anthropic's server-side [tool search](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) works on this endpoint. Pass a `tool_search_tool_*` tool alongside your catalog and mark the tools that should load on demand with `defer_loading: true`:
```json
{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{ "role": "user", "content": "What is the weather in Paris?" }],
"tools": [
{
"type": "tool_search_tool_regex_20251119",
"name": "tool_search_tool_regex"
},
{
"name": "get_weather",
"description": "Get the weather at a specific location",
"input_schema": { "type": "object" },
"defer_loading": true
}
]
}
```
Deferred tools stay out of the rendered tools section, so adding one does not invalidate an existing prompt cache. The response carries the `server_tool_use` and `tool_search_tool_result` blocks; replay them verbatim on the next request and Anthropic keeps expanding the `tool_reference` entries they carry, so Claude reuses a discovered tool instead of searching again. `tool_reference` blocks returned from your own client-side search inside a `tool_result` are forwarded unchanged too.
Send every tool definition on every request, including the deferred ones — Anthropic needs them server-side to run the search. At least one tool must stay non-deferred (normally the tool search tool itself), and a tool cannot carry both `defer_loading: true` and `cache_control`.
**Where it works.** Tool search reaches the provider on the Anthropic API and on Anthropic models served through Google Cloud, and it needs a Claude 4.5-generation model or newer — older Claude models reject it upstream. On every other provider, including Anthropic models on AWS Bedrock, the tool search tool and `defer_loading` are dropped and all tools are sent eagerly. The request still succeeds, it just loses the cache and token savings, so pin the provider (`anthropic/claude-sonnet-4-6`) when those savings matter.
Bedrock is a transport limitation rather than a missing capability: Anthropic
exposes server-side tool search there only through the InvokeModel API, and
the gateway routes Bedrock through the Converse API.
### Auto Mode Safeguard Review [#auto-mode-safeguard-review]
In [auto mode](https://code.claude.com/docs/en/permission-modes#server-side-classifier-review), Claude Code asks Anthropic to review risky tool calls as part of its normal requests, at no charge: it sends a `safeguards` field with a paired `anthropic-beta` value, and Anthropic returns the verdicts in `safeguard_results` (on the final `message_delta` event when streaming). The gateway forwards both halves to the Anthropic API and returns the verdicts unchanged, so Claude Code keeps using the free server-side checks instead of its own billed classifier requests. Run `/status` in Claude Code to confirm: the **Auto mode server** row reads `Enabled`.
**Routing requirements.** A nonempty `safeguards` field paired with its dated beta restricts automatic selection and retries to providers that support forwarding server-side review. Currently this is the Anthropic API. An incompatible saved session pin is replaced; an explicitly requested incompatible provider, or automatic selection with no compatible provider, returns HTTP `400`, including for streaming requests. A beta header alone does not restrict routing.
Upstream availability still depends on the model, credentials, and rollout. If Anthropic returns no available verdict, Claude Code can fall back to its own billed classifier requests. To use another provider deliberately, set `CLAUDE_CODE_AUTO_MODE_SERVER=0` before starting Claude Code; its client-side classifier requests are billed normally.
### Gateway Response Cache [#gateway-response-cache]
If [gateway caching](https://docs.llmgateway.io/features/caching/gateway-caching) is enabled on the project, a repeated request with the same [cache-key fields](https://docs.llmgateway.io/features/caching/gateway-caching#cache-key-generation) — resolved provider and model, messages, and the other keyed parameters; fields outside the cache key and insignificant JSON whitespace don't affect matching, but the key order inside message and tool objects does, and a request routed to a different provider is a cache miss — is replayed from cache instead of being sent upstream. The replayed body keeps the original `id`, content, and token counts, so check the `x-llmgateway-cache: HIT` response header or `metadata.cached`. Send `x-no-cache: true` to bypass the cache for a single request.
# LLM Gateway Authentication, API Keys & IAM Rules
URL: https://docs.llmgateway.io/features/api-keys
API keys are the primary method for authenticating with the LLM Gateway. This guide covers creating API keys, managing them, and configuring IAM rules for fine-grained access control.
## Overview [#overview]
LLM Gateway provides comprehensive API key management with the following features:
* **Basic API Key Management**: Create, list, rename, update, and delete API keys
* **Usage Limits**: Set lifetime and recurring spending limits on individual API keys
* **Expiration (TTL)**: Give a key a time-to-live so it disables itself automatically
* **Rotation (Rolling)**: Replace a key's secret in place without losing its settings or history
* **IAM Rules**: Fine-grained access control for models, providers, and pricing
* **Usage Tracking**: Monitor API key usage and costs
* **Status Management**: Enable/disable keys without deletion
### Related key types [#related-key-types]
This page covers gateway API keys (`llmgtwy_…`), the keys you send to the
gateway as a bearer token. LLM Gateway also issues three other kinds of keys,
each with its own page:
| Key | What it is for |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
| [Master keys](https://docs.llmgateway.io/features/master-keys) | Manage projects, gateway API keys, IAM rules, and custom models programmatically |
| [Provider keys](https://docs.llmgateway.io/learn/provider-keys) | Bring your own upstream provider credentials so requests bill to your provider account |
| [Platform secret keys](https://docs.llmgateway.io/features/embeddable-payments) | Mint end-user sessions from your backend when using the Payments SDK (`sk_…`) |
## Creating API Keys [#creating-api-keys]
### Via Dashboard [#via-dashboard]
1. Navigate to your project in the LLM Gateway dashboard
2. Go to the **API Keys** section
3. Click **Create API Key**
4. Provide a description for your key
5. Optionally set an all-time usage limit
6. Optionally set a recurring usage limit such as `$10 / day` or `$500 / month`
7. Optionally set an expiration (TTL) such as `30 minutes`, `12 hours`, or `7 days`
8. Click **Create**
API keys are shown in full only once during creation. Make sure to copy and
store them securely. New and rolled secrets are stored only as keyed
HMAC-SHA-256 fingerprints, and authentication compares the fingerprint of the
presented secret.
### Programmatically [#programmatically]
Gateway API keys can also be created, listed, updated, and deleted without the
dashboard using a [master key](https://docs.llmgateway.io/features/master-keys) — useful when you provision
a key per customer or per environment from your own backend:
```bash
curl -X POST https://internal.llmgateway.io/v1/master/keys \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "proj_...",
"description": "Customer ACME — production key",
"periodUsageLimit": "10.00",
"periodUsageDurationValue": 1,
"periodUsageDurationUnit": "day"
}'
```
Usage limits and IAM rules can be configured through the master key API as well.
Expiration (TTL) and rotation are currently dashboard-only. See the [master key
API reference](https://docs.llmgateway.io/features/master-keys#create-a-gateway-api-key) for the full
endpoint list.
## Using API Keys [#using-api-keys]
Once you have an API key, use it in the `Authorization` header of your requests:
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer llmgtwy_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
Alternatively, send the key in an `x-api-key` header — the convention Anthropic-style clients use, including on the [`/v1/messages` endpoint](https://docs.llmgateway.io/features/anthropic-endpoint). When both headers are present, `Authorization: Bearer` takes precedence.
To show DevPass allowance meters in an integration, see the [DevPass Usage
API](https://docs.llmgateway.io/developers/devpass-usage).
## Renaming API Keys [#renaming-api-keys]
A key's name is only a label, so you can change it at any time — from the
dashboard, or with the `description` field on [`PATCH
/v1/master/keys/{id}`](https://docs.llmgateway.io/features/master-keys#update-a-gateway-api-key) — without
affecting the secret, its usage history, limits, or IAM rules.
## Budget alerts [#budget-alerts]
Enable **API key budgets** in the dashboard's notification settings to receive in-app or email warnings as a key approaches its lifetime or recurring limit. Choose a threshold from 50% to 100%; the default is 80%. See [Notifications](https://docs.llmgateway.io/features/notifications) for delivery and access rules.
## Disabling/Enabling API Keys [#disablingenabling-api-keys]
You can disable an API key to stop it from being used, but the key is not deleted and can be re-enabled later.
## Rotating (Rolling) API Keys [#rotating-rolling-api-keys]
Rolling a key generates a **new secret for the same key** and invalidates the old
one immediately. Everything else about the key is preserved: its name, usage
history and statistics, all-time and recurring limits (including the active
period window), IAM rules, and expiration.
Use this when a secret may have been exposed — in a commit, a log, a CI
artifact, or a shared environment — and you want to cut off the leaked value
without losing the key's spend tracking or access rules.
1. Open the **API Keys** page and pick the key's actions menu
2. Choose **Roll Key** and confirm
3. Copy the new secret and update every client that used the old one
The old secret stops working the moment the key is rolled, and the new secret
is shown only once. Roll during a window where you can update your clients
promptly.
Requests made with the old secret are rejected with a `401 Unauthorized`.
Rolling is limited to regular gateway API keys — the auto-generated Lounge
key cannot be rolled, and Payments SDK [platform secret
keys](https://docs.llmgateway.io/features/embeddable-payments) have their own lifecycle.
## Expiration (TTL) [#expiration-ttl]
You can give an API key a **time-to-live (TTL)** when you create it. Set how long
the key should live — in **minutes**, **hours**, or **days** — and it will be
disabled automatically once that time passes. This is ideal for short-lived
integrations, demos, CI jobs, and temporary access.
* A key works normally until its expiration time
* Once expired, the gateway rejects requests with that key with a `401 Unauthorized`
* A background job marks expired keys as **inactive**, so the dashboard reflects
the disabled state
* Keys created without a TTL never expire (the default)
### Reactivating an Expired Key [#reactivating-an-expired-key]
An expired key is paused, not deleted. To bring it back online you must reactivate
it **with a new future expiration** — an expired key cannot be re-enabled while its
TTL is still in the past. Keys that have no TTL, or whose TTL is still in the
future, can be enabled and disabled freely without setting a new expiration.
Expiration is independent of usage limits. A key can hit its TTL before, or
instead of, reaching a spend cap.
## Usage Limits [#usage-limits]
Usage is tracked per API key on the API Keys page. Usage includes both costs
from LLM Gateway credits and usage from your own provider keys when applicable,
giving you complete visibility into total spending per key.
You can set two independent limits for each key:
* **All-time usage limit**: A lifetime spend cap
* **Recurring usage limit**: A spend cap that resets every configured hour, day, week, or month
When a key reaches either limit, requests using that key return `401
Unauthorized` until the key is updated or, for recurring limits, the next
usage window starts. This is separate from IAM rule violations, which return
`403 Forbidden`.
Recurring windows support:
* Minimum duration: **1 hour**
* Maximum duration: **12 months**
* Units: **hour**, **day**, **week**, **month**
For the dashboard walkthrough and field-by-field details, see [API Keys in
Learn](https://docs.llmgateway.io/learn/api-keys).
## IAM Rules [#iam-rules]
IAM (Identity Access Management) rules provide fine-grained access control over what models, providers, and pricing tiers an API key can access.
### Rule Types [#rule-types]
#### Model Access Rules [#model-access-rules]
Control access to specific models:
* **Allow Models**: Only allow access to specific models
* **Deny Models**: Block access to specific models
#### Provider Access Rules [#provider-access-rules]
Control access to specific providers:
* **Allow Providers**: Only allow access to specific providers
* **Deny Providers**: Block access to specific providers
Provider rules can also target your organization's own [custom providers](https://docs.llmgateway.io/features/custom-providers). The generic `custom` entry matches every custom provider, while a `custom:` entry (offered in the selector for each of your custom providers) matches only the custom provider with that name. Model rules can likewise reference a custom-catalog model as `/`.
#### Pricing Rules [#pricing-rules]
Control access based on model pricing:
* **Allow Pricing**: Set constraints on what pricing tiers are allowed
* **Deny Pricing**: Block specific pricing tiers
* **Free vs Paid**: Allow or deny access to free vs paid models
#### IP Address Rules [#ip-address-rules]
IP address rules are available on the **Enterprise** plan only. Contact us at
[contact@llmgateway.io](mailto:contact@llmgateway.io) to enable them for your organization.
Restrict where the API key can be used from by source IP, using CIDR ranges:
* **Allow IP Ranges (CIDR)**: Only permit requests from the listed IPv4/IPv6 CIDRs
* **Deny IP Ranges (CIDR)**: Block requests from the listed IPv4/IPv6 CIDRs
Both IPv4 (e.g. `192.0.2.0/24`) and IPv6 (e.g. `2001:db8::/32`) ranges are supported, and you can mix both in a single rule. To restrict to a single address, use a `/32` (IPv4) or `/128` (IPv6) prefix.
The gateway reads the client IP from a single header, named by the `CLIENT_IP_HEADER` environment variable. On the hosted service the load balancer overwrites that header on every request, so a value sent by the client cannot spoof the address. Self-hosted deployments default to `X-Forwarded-For`; point `CLIENT_IP_HEADER` at a header your reverse proxy overwrites rather than appends to.
Compatibility endpoints preserve this client IP through internal forwarding, so IP rules apply to Messages, Responses (including compaction), Images, and AI SDK requests as well as Chat Completions. Lounge, docs Ask AI and the API explorer, admin reply generation, API-side generation and embeddings, and MCP tools also forward the configured client-IP header.
For self-hosted server-to-server calls, set `GATEWAY_BACKEND_URL` to a trusted gateway origin that preserves this header. It takes precedence over `GATEWAY_URL` for generation requests; browser-facing and signed content URLs still use `GATEWAY_URL`. Sending these calls through an edge that overwrites the header records the calling server’s IP instead. MCP generation uses `GATEWAY_BACKEND_URL`, and MCP account and usage calls use `API_BACKEND_URL`. These explicitly configured backends may use HTTP within a trusted private network and must preserve the header. Public fallbacks (`GATEWAY_URL` and `API_URL`) and the optional `MCP_GATEWAY_URL` override require HTTPS. Keep backend origins private to trusted services.
When an `allow_ip_cidrs` rule is configured and the gateway cannot determine the client IP, the request is denied. Invalid CIDR syntax is rejected at rule-creation time with a `400` error.
### Combining Multiple Rules [#combining-multiple-rules]
* **Allow rules of the same type are unioned**: a request passes if it matches *any* of them. For example, one `allow_models` rule with `["claude-opus-4-6"]` and another with `["claude-fable-5"]` allow both models — exactly as if you had a single rule listing both.
* **Allow rules of different types are combined with AND**: the request must satisfy every configured allow rule type (e.g. the model must be in the allowed models *and* served by an allowed provider).
* **Deny rules always apply**: a request matching any deny rule is rejected, regardless of allow rules.
* **Moderation is special-cased**: `/v1/moderations` runs a fixed moderation model that cannot appear in model allowlists, so only provider and IP rules apply to it — model and pricing rules are skipped.
## Member-Level IAM Rules [#member-level-iam-rules]
The same rule types can also be configured **per organization member** by owners and admins on the [Team page](https://docs.llmgateway.io/learn/team) (admins cannot modify an owner's rules). Member-level rules act as an organization-wide ceiling for that member:
* A request must pass **both** the member's rules and the API key's rules. Within each level, rules combine exactly as described above.
* Key rules can only **narrow** access further — they can never grant anything the member's rules deny. For example, if an admin restricts a member to a single approved [provider](https://llmgateway.io/providers) with an `allow_providers` rule, the member can create a key rule allowing only a specific model from that provider, but a key rule allowing any other provider has no effect.
* A key with **no rules of its own** is still fully constrained by its owner's member-level rules.
* Member-level rules apply to all regular API keys created by that member, across every project in the organization.
When a request is denied by a member-level rule, the `403` error message states that the restriction is an organization member IAM rule set by the org admin (rather than the key's own IAM configuration), so key holders know who to contact.
Member-level rules can also be managed programmatically via the [master key API](https://docs.llmgateway.io/features/master-keys#member-iam-rules), addressing members by membership id or email.
## Team-Level IAM Rules [#team-level-iam-rules]
The same rule types can additionally be configured on an **organization team**. Team rules apply to members with the `developer` role who are assigned to that team, and they are evaluated **before** member and key rules: a request must pass the team's rules, then the member's rules, then the key's rules, and each layer can only narrow what the previous one allowed. When a request is denied by a team-level rule, the `403` error message states that the restriction is inherited from an organization team IAM rule set by the org admin.
## Error Handling [#error-handling]
When API keys encounter IAM rule violations, the API returns a `403` with the standard OpenAI error envelope:
```json
{
"error": {
"message": "Access denied: Model gpt-4 is not in the allowed models list",
"type": "invalid_request_error",
"param": null,
"code": "permission_denied"
}
}
```
Common error scenarios:
* Model not allowed by IAM rules
* Provider blocked by IAM rules
* Pricing limits exceeded
* API key disabled or deleted
* API key expired (TTL passed)
* API key rolled, so the old secret is no longer valid
* Usage limit reached
## Migration from Legacy Keys [#migration-from-legacy-keys]
If you have existing API keys without IAM rules:
1. **Backward Compatibility**: Existing keys continue to work without restrictions
2. **Gradual Migration**: Add IAM rules incrementally
3. **Testing**: Test IAM rules in development before applying to production
4. **Monitoring**: Monitor for access denied errors after implementing rules
API keys without IAM rules have unrestricted access to all models and
providers.
# Audit Logs
URL: https://docs.llmgateway.io/features/audit-logs
Audit logs provide complete visibility into all actions within your organization. Track who did what, when, and to which resource.
Audit logs are available on the [**Enterprise
plan**](https://llmgateway.io/enterprise) for organization owners and admins.
## What's Tracked [#whats-tracked]
Every significant action is logged with detailed metadata:
| Field | Description |
| ----------------- | -------------------------------------------------------- |
| **Timestamp** | When the action occurred |
| **User** | Who performed the action (name and email) |
| **Action** | What was done (e.g., `api_key.create`, `project.update`) |
| **Resource Type** | Category of the affected resource |
| **Resource ID** | Unique identifier of the affected resource |
| **Details** | Additional context like resource names or changed fields |
## Tracked Actions [#tracked-actions]
The categories below are illustrative, not exhaustive — many more actions are recorded (master keys, custom models, organization teams and team IAM rules, SSO and SCIM events, invites, plan changes, refunds, and others). The action filter dropdown in the dashboard lists the full set.
### Organization Management [#organization-management]
* `organization.update` — Organization settings changed
* `organization.delete` — Organization deleted
### Project Management [#project-management]
* `project.create` — New project created
* `project.update` — Project settings changed
* `project.delete` — Project deleted
### Team Management [#team-management]
* `team_member.add` — New member invited
* `team_member.update` — Member role changed
* `team_member.remove` — Member removed
### API Key Management [#api-key-management]
* `api_key.create` — New API key created
* `api_key.update_status` — API key enabled/disabled
* `api_key.update_limit` — Usage limit changed
* `api_key.delete` — API key deleted
* `api_key.iam_rule.create` — IAM rule added
* `api_key.iam_rule.update` — IAM rule modified
* `api_key.iam_rule.delete` — IAM rule removed
### Provider Key Management [#provider-key-management]
* `provider_key.create` — Provider key added
* `provider_key.update` — Provider key changed (status, name, limits, and other settings; base URL and token rotation when done through the master key API). Custom providers are recorded as `provider_key.*` events with `provider: "custom"`
* `provider_key.delete` — Provider key removed
### Billing Events [#billing-events]
* `subscription.create` — Subscription started
* `subscription.cancel` — Subscription cancelled
* `subscription.resume` — Subscription resumed
* `payment.credit_topup` — Credits purchased
## Filtering and Search [#filtering-and-search]
Filter logs by:
* **Action** — Specific action type
* **Resource Type** — Category of resource
* **User** — Who performed the action
* **Date Range** — Time period
## Data Retention [#data-retention]
Audit logs are retained for **at least 90 days** on the Enterprise plan — there is currently no automatic expiry.
## Access Control [#access-control]
Only organization **owners** and **admins** can view audit logs. This ensures sensitive activity data is only visible to authorized personnel.
## Get Started [#get-started]
Audit logs are an Enterprise feature. [Contact us](https://llmgateway.io/enterprise) to enable Enterprise for your organization.
# Compliance
URL: https://docs.llmgateway.io/features/compliance
Provider compliance policies let you guarantee that requests are only ever routed to providers that meet your organization's regulatory requirements. When a request would be routed to a provider that doesn't meet the policy, the gateway blocks it **before any data leaves the gateway**.
The complete provider compliance policy is available on
[**Enterprise**](https://llmgateway.io/enterprise). DevPass offers only **No
AI training**.
For providers managed entirely through Airside, policy checks use the active
carrier's self-declared profile, including subsequent edits. No separate
compliance review is required. Missing facts fail the corresponding policy
requirement; provider allowlists and blocklists keep using the same provider ID.
## Requirements [#requirements]
Enable a policy under **Settings → Compliance** in the dashboard and toggle the requirements you need:
In DevPass, enable **No AI training** directly under **Settings**. It uses the same fail-closed provider policy described below, but the other requirements are not available on DevPass.
| Requirement | A provider is allowed when… |
| ----------------------------- | ---------------------------------------------------------------- |
| **Zero data retention (ZDR)** | it does not log prompts and declares a zero-day retention period |
| **No training on prompts** | it does not train on API prompts |
| **No stealth providers** | it is not a stealth provider |
| **GDPR compliant** | it is GDPR compliant |
| **SOC 2 (Type 1 or 2)** | it holds a SOC 2 report of any type |
| **SOC 2 Type 2** | it holds a SOC 2 Type 2 report specifically |
| **ISO 27001** | it holds an ISO 27001 certification |
| **SOC 2 Type 2 or ISO 27001** | it holds either a SOC 2 Type 2 report or ISO 27001 |
Every requirement is **fail-closed**: a provider passes only if its published data policy explicitly satisfies the requirement. If an attribute is unknown for a provider, that provider is treated as non-compliant.
Before enabling **Zero data retention (ZDR)**, set the organization's data retention to **Metadata Only** and disable response caching in every project. While ZDR is active, **Retain All Data** and project response caching are unavailable, gateway response caching is bypassed, provider prompt-cache markers are stripped, Responses API requests must set `store: false`, and asynchronous video generation is unavailable because video jobs require temporary output storage. Disable ZDR before turning payload retention or caching back on. ZDR is a separate Enterprise compliance policy and is unchanged by the Enterprise requirement for **Retain All Data** itself.
Existing policies that use the deprecated **No prompt logging** rule continue to enforce their original provider-routing behavior. The legacy rule is no longer available for new configuration and does not enable the gateway-level ZDR controls described above.
Stealth providers are undisclosed platforms that LLM Gateway routes to without naming the operator, so their data policy and headquarters are unknown. That already makes them fail every certification and data-policy requirement above, but **No stealth providers** excludes them explicitly — useful when you want them gone without turning on any other requirement.
The settings page shows a live **Provider Impact** preview of which providers are allowed (green) and which are blocked (red) under the current policy, so you can see the impact before saving. Hovering a blocked provider lists exactly which requirements it does not meet — a missing certification, its data policy, its headquarters country, or a provider-list restriction.
## Provider headquarters [#provider-headquarters]
Beyond certifications, you can restrict routing by the country a provider is headquartered in. The **Provider Headquarters** card presents a country selector, and requests are only routed to providers based in a selected country.
The selector only offers countries that are referenced by a catalogue provider or active Airside profile — browse the [providers directory](https://llmgateway.io/providers) to see the current set and each provider's headquarters. Selecting no country applies no location restriction. The country filter is also fail-closed: when at least one country is selected, a provider whose headquarters is unknown is treated as non-compliant.
The country filter composes with the certification and data-policy requirements above — a provider must satisfy all active requirements to be allowed.
## Provider & model restrictions [#provider--model-restrictions]
Beyond attribute-based requirements, the **Provider & Model Restrictions** card lets you block or allow individual providers and models:
* **Blocked providers / blocked models** — deny lists. A listed provider or model is always blocked, even when it satisfies every requirement above.
* **Allowed providers / allowed models** — fine-grained allow lists. When non-empty, only the listed providers (or models) may be used; everything else is blocked. An empty list applies no restriction.
Deny lists always win over allow lists, and both compose with the requirements above — an allow-listed provider must still satisfy every active certification, data-policy, and country requirement.
A non-empty allowed-providers list blocks **every** provider not on it,
regardless of certifications or data policy. If you allow-list only a custom
provider, all catalogue providers show as blocked with the reason "Not on the
allowed-providers list" — add any additional provider you want to use to the
allow list.
The selectors include your organization's own [custom providers](https://docs.llmgateway.io/features/custom-providers) (stored as `custom:` refs) and their custom-catalog models (stored as `/` refs), so a specific custom deployment can be blocked or allow-listed individually just like a catalogue provider.
### Choosing a compliant provider [#choosing-a-compliant-provider]
The provider dropdowns are policy-aware, so you can tell **before** adding a provider to a list whether it satisfies your policy:
* A **green shield** marks a provider that meets every active certification, data-policy, and headquarters requirement.
* A **red shield** marks a provider that does not; the requirements it misses (for example "May log prompts" or "Headquartered in China, which is not an allowed country") are listed under its name.
* The **"Only providers that meet policy requirements"** toggle at the top of the dropdown hides incompatible providers entirely.
These indicators evaluate the requirements only — deliberately ignoring the allowed/blocked lists themselves — so an active allow list never paints every other provider red while you're deciding what to add to it. Your own custom providers are evaluated against their [self-attested posture](#custom-providers); one without an attestation on file shows "No compliance attestation on file".
Elsewhere in the dashboard (for example the API-key provider filter), the colored dot next to a provider is simply the provider's **brand color** and carries no compliance meaning.
These restrictions are **organization-wide** and take precedence over member-level and API-key-level [IAM rules](https://docs.llmgateway.io/features/api-keys): they are enforced after IAM evaluation, so no user-, team-, or key-level allow rule can grant access to a provider or model the compliance policy excludes.
## Enforcement [#enforcement]
When no available provider for a model meets the policy — or a pinned provider is non-compliant — the gateway returns a `403`:
```json
{
"error": {
"message": "This request was blocked by your organization's provider compliance policy. No available provider for deepseek-v3.2 meets the required certifications or provider/model restrictions. Contact your LLMGateway admin to adjust the policy."
}
}
```
This applies to both routing modes:
* **Automatic routing** — non-compliant providers are removed from the candidate set, and the request is blocked if none remain.
* **Pinned providers** — a request such as `deepseek/deepseek-v3.2` is blocked when that specific provider does not meet the policy.
Each block is recorded as a **security event** so administrators can review what was rejected and why.
## Custom providers [#custom-providers]
[Custom providers](https://docs.llmgateway.io/features/custom-providers) point at infrastructure your organization operates itself (for example, models hosted in your own cloud account), so they have no entry in the provider catalogue and no published data policy. Under any enabled compliance policy they are therefore **blocked by default** — the same fail-closed rule that applies to catalogue providers with unknown attributes.
To route through a custom provider while a policy is active, an organization owner or admin can record a **self-attestation** of that provider key's compliance posture under **Models → Compliance attestation** in the dashboard. The attestation covers the same attributes as a catalogue data policy — SOC 2 report type, ISO 27001, GDPR, training on API prompts, prompt logging, retention period, and the country the deployment is operated from — and the gateway evaluates it against your policy with **identical fail-closed rules**: an attribute left "unknown" never satisfies a requirement, and only an explicit "No" satisfies requirements like "No training on prompts". Clearing the attestation restores the blocked state.
LLMGateway does not verify attestations — they are your organization's own
assertion about infrastructure you operate. Every attestation change is
recorded in the audit log with the attesting user and timestamp.
Attestations only apply to custom provider keys. They can never be used to clear a catalogue provider that fails your policy.
**Known limitation:** the country selector in the compliance policy only offers countries referenced by catalogue providers or active Airside profiles (see the [providers directory](https://llmgateway.io/providers)). A custom deployment attested to a country outside that set cannot be allowed while a country restriction is active.
## Compliance alerts [#compliance-alerts]
Once a policy is saved and enabled, owners and admins on the Enterprise plan can configure **Compliance Alerts** at the bottom of the Compliance page:
* **Watched models**: pick models that no compliant provider serves yet, up to 100 per organization. Retired models and models blocked by your own model lists are not offered. When a compliant provider starts serving one, LLM Gateway notifies you once — including when your own policy change makes it available. If the model later loses its last compliant provider, the watch re-arms. A model that is already available when you add it is recorded as available, so it sends no initial alert; it can still alert later if it loses its last compliant provider and becomes available again.
* **Downgrades** (on by default): get notified when a provider stops meeting your policy, for example because it loses a certification or starts training on API data. When the audience is owners and admins, that alert also lists the models your organization used on the provider in the last 30 days that no longer have a compliant provider; it is left out for the everyone audience, because it spans every project. A watched model losing its last compliant provider is announced separately. Changes to your own policy never trigger downgrade alerts, and the first check after enabling alerts only records the current state.
* **Recipients**: choose the lowest role that receives alerts — owners only, owners and admins, or everyone in the organization. Higher roles are always included. Owners and admins are linked to this page; other recipients land on the Models page, which shows the same eligibility. Watching the first model saves the defaults: owners and admins, in-app and email delivery, downgrade alerts on.
* **Delivery**: in-app, email, and Slack. In-app and email reach each recipient individually and email needs a verified address; Slack is posted once per organization. Each recipient can turn off in-app or email delivery per alert type under [notification settings](https://docs.llmgateway.io/features/notifications) in the dashboard bell.
Alerts are checked every minute against the saved policy. Disabling the policy or leaving the Enterprise plan stops the checks.
### Slack [#slack]
Create a Slack [incoming webhook](https://api.slack.com/messaging/webhooks) for the channel that should receive alerts and save its URL under **Notification Channels** on the organization **Preferences** page — only `https://hooks.slack.com/services/…` URLs are accepted. Once saved, use **Send test message** to verify it, then enable Slack in the Compliance Alerts card. The webhook URL is stored encrypted and only shown masked. Failed posts are retried up to five times within 24 hours, and removing the webhook also turns Slack off in the alert settings.
## Safety identifiers [#safety-identifiers]
Providers that offer an abuse-attribution identifier receive an opaque, random value that LLM Gateway generates once per organization. It contains no personal data — no email address, user id or organization name — and it is the only thing that ties a report of abusive traffic back to an account.
Whether LLM Gateway forwards this identifier depends on the upstream API. Check the [providers directory](https://llmgateway.io/providers) and open a provider to see its current behavior. Any `safety_identifier` you set on a request yourself is ignored — the value is always the one derived from your organization.
## Access Control [#access-control]
Only organization **owners** and **admins** can view and change the compliance policy.
Project-scoped **developer** members cannot see the policy itself, but they can browse the org's available providers and models — including custom providers and their model catalogs — read-only on the **Models** page. This is the recommended way for developers to discover what they can call without being granted additional permissions.
## Related [#related]
* [Notifications](https://docs.llmgateway.io/features/notifications) — per-user in-app and email delivery preferences, including compliance alerts.
* [Org Models Directory](https://docs.llmgateway.io/features/models-directory) — see per-model eligibility under the active policy in the dashboard.
* [Data Retention](https://docs.llmgateway.io/features/data-retention) — control whether request and response payloads are stored (Retain All Data is Enterprise only).
* [Guardrails](https://docs.llmgateway.io/features/guardrails) — detect and block harmful or sensitive content.
## Get Started [#get-started]
DevPass subscribers can configure **No AI training** under **Settings**. [Contact us](https://llmgateway.io/enterprise) for the complete provider compliance policy.
# Cost Breakdown
URL: https://docs.llmgateway.io/features/cost-breakdown
LLM Gateway provides real-time cost information for each API request directly in the response's `usage` object. This allows you to track costs programmatically without needing to query the dashboard.
Cost breakdown is available for all users on both hosted and self-hosted
deployments.
## Response Format [#response-format]
API responses include cost fields in the `usage` object:
```json
{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1234567890,
"model": "openai/gpt-4o",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 15,
"total_tokens": 25,
"cost": 0.000125,
"cost_details": {
"upstream_inference_cost": 0.000125,
"upstream_inference_prompt_cost": 0.000025,
"upstream_inference_completions_cost": 0.0001,
"total_cost": 0.000125,
"input_cost": 0.000025,
"output_cost": 0.0001,
"cached_input_cost": 0,
"request_cost": 0,
"web_search_cost": 0,
"image_input_cost": null,
"image_output_cost": null,
"data_storage_cost": 0.00000025
},
"prompt_tokens_details": {
"cached_tokens": 0,
"cache_write_tokens": 0,
"audio_tokens": 0,
"video_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"image_tokens": 0,
"audio_tokens": 0
}
}
}
```
## Cost Fields [#cost-fields]
| Field | Description |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cost` | Total inference cost for the request in USD |
| `cost_details.upstream_inference_cost` | Combined upstream inference cost in USD (prompt + completions) |
| `cost_details.upstream_inference_prompt_cost` | Upstream cost for prompt tokens in USD (includes cached prompt discount) |
| `cost_details.upstream_inference_completions_cost` | Upstream cost for completion tokens in USD |
| `cost_details.total_cost` | Total request cost in USD (LLM Gateway extended field) |
| `cost_details.input_cost` | Cost for non-cached prompt tokens in USD |
| `cost_details.output_cost` | Cost for completion tokens in USD |
| `cost_details.cached_input_cost` | Cost for cached prompt tokens in USD |
| `cost_details.cache_write_input_cost` | Cost for prompt tokens written to the provider cache in USD, billed at the provider's cache-write premium (e.g. 1.25x for 5m / 2x for 1h on Anthropic) |
| `cost_details.request_cost` | Per-request flat fee in USD (when the model applies one) |
| `cost_details.web_search_cost` | Cost for web search tool calls in USD |
| `cost_details.image_input_cost` | Cost for image inputs in USD |
| `cost_details.image_output_cost` | Cost for image outputs in USD |
| `cost_details.data_storage_cost` | Storage cost for retained request/response payloads in USD |
## Token Detail Fields [#token-detail-fields]
The `usage` object also includes detailed token counters that mirror OpenAI's extended format:
| Field | Description |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `prompt_tokens_details.cached_tokens` | Number of prompt tokens served from the provider's prompt cache |
| `prompt_tokens_details.cache_write_tokens` | Number of prompt tokens written into the provider's prompt cache |
| `prompt_tokens_details.cache_creation_tokens` | Alias of `cache_write_tokens`, matching Anthropic's naming |
| `prompt_tokens_details.cache_creation` | Per-TTL breakdown of cache writes (`ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens`), present when a cache write occurred |
| `prompt_tokens_details.audio_tokens` | Number of audio prompt tokens |
| `prompt_tokens_details.video_tokens` | Number of video prompt tokens |
| `completion_tokens_details.reasoning_tokens` | Number of reasoning tokens produced by reasoning models |
| `completion_tokens_details.image_tokens` | Number of image tokens produced |
| `completion_tokens_details.audio_tokens` | Number of audio tokens produced |
## Streaming Responses [#streaming-responses]
Cost information is also available in streaming responses. The cost fields are included in the final usage chunk sent before the `[DONE]` message:
```
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","choices":[...],"usage":{"prompt_tokens":10,"completion_tokens":15,"total_tokens":25,"cost":0.000125,"cost_details":{"upstream_inference_cost":0.000125,"upstream_inference_prompt_cost":0.000025,"upstream_inference_completions_cost":0.0001,"total_cost":0.000125,"input_cost":0.000025,"output_cost":0.0001,"cached_input_cost":0,"request_cost":0,"web_search_cost":0,"image_input_cost":null,"image_output_cost":null,"data_storage_cost":0.00000025}}}
data: [DONE]
```
## Example: Tracking Costs in Code [#example-tracking-costs-in-code]
Here's an example of how to track costs programmatically using the cost breakdown feature:
```typescript
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LLM_GATEWAY_API_KEY,
baseURL: "https://api.llmgateway.io/v1",
});
async function trackCosts() {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
});
const usage = response.usage as any;
if (usage.cost !== undefined) {
console.log(`Request cost: $${usage.cost.toFixed(6)}`);
console.log(
` Prompt: $${usage.cost_details.upstream_inference_prompt_cost.toFixed(6)}`,
);
console.log(
` Completions: $${usage.cost_details.upstream_inference_completions_cost.toFixed(6)}`,
);
const cachedTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
if (cachedTokens > 0) {
console.log(` Cached prompt tokens: ${cachedTokens}`);
}
}
return response;
}
```
## Use Cases [#use-cases]
### Budget Monitoring [#budget-monitoring]
Track costs in real-time and implement budget limits in your application:
```typescript
let totalSpent = 0;
const BUDGET_LIMIT = 10.0; // $10 budget
async function makeRequest(messages: Message[]) {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages,
});
const cost = (response.usage as any).cost || 0;
totalSpent += cost;
if (totalSpent > BUDGET_LIMIT) {
throw new Error(`Budget exceeded: $${totalSpent.toFixed(2)}`);
}
return response;
}
```
### Per-User Cost Allocation [#per-user-cost-allocation]
Track costs per user for billing or analytics:
```typescript
const userCosts: Map = new Map();
async function makeRequestForUser(userId: string, messages: Message[]) {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages,
});
const cost = (response.usage as any).cost || 0;
const currentCost = userCosts.get(userId) || 0;
userCosts.set(userId, currentCost + cost);
return response;
}
```
### Cost Analytics [#cost-analytics]
Aggregate costs by model, time period, or any other dimension:
```typescript
interface CostEntry {
timestamp: Date;
model: string;
promptCost: number;
completionsCost: number;
totalCost: number;
}
const costLog: CostEntry[] = [];
async function loggedRequest(model: string, messages: Message[]) {
const response = await client.chat.completions.create({
model,
messages,
});
const usage = response.usage as any;
costLog.push({
timestamp: new Date(),
model: response.model,
promptCost: usage.cost_details?.upstream_inference_prompt_cost || 0,
completionsCost:
usage.cost_details?.upstream_inference_completions_cost || 0,
totalCost: usage.cost || 0,
});
return response;
}
```
## Self-Hosted Deployments [#self-hosted-deployments]
If you're running a self-hosted LLM Gateway deployment, cost breakdown is always included in API responses regardless of plan. This allows you to track internal costs and allocate them across teams or projects.
# Custom Providers
URL: https://docs.llmgateway.io/features/custom-providers
LLMGateway supports integrating custom OpenAI-compatible providers, allowing you to use any API that follows the OpenAI chat completions format. This feature is perfect for:
* Private or self-hosted LLM deployments
* Specialized AI providers not natively supported
* Internal AI services within your organization
* Testing against different model endpoints
Custom providers are an **Enterprise** feature. Custom providers created
before this requirement keep serving requests on any plan and can still be
deactivated or deleted, but creating or editing them requires an enterprise
plan.
Custom providers must be OpenAI-compatible, supporting the
`/v1/chat/completions` endpoint format.
## Quick Setup [#quick-setup]
### 1. Add a Custom Provider Key [#1-add-a-custom-provider-key]
Navigate to your organization's provider settings and add a custom provider via the UI (requires an enterprise plan).
Provide a lowercase name, OpenAI-compatible base URL, and API token for the custom provider.
### 2. Make Requests [#2-make-requests]
Once configured, make requests using the format `{customName}/{modelName}`:
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mycompany/custom-gpt-4",
"messages": [
{
"role": "user",
"content": "Hello from my custom provider!"
}
]
}'
```
## Configuration Requirements [#configuration-requirements]
### Custom Provider Name [#custom-provider-name]
* **Format**: Lowercase letters (`a-z`) with optional single hyphens between them
* **Examples**: `mycompany`, `internal`, `my-company`, `eu-west`
* **Invalid**: `MyCompany`, `my_company`, `123test`, `-mycompany`, `my-`, `my--company`
* **Reserved**: `dynamic` is rejected (it is the [dynamic routes](https://docs.llmgateway.io/features/dynamic-routes) model prefix), and the name must be unique within your organization
The custom provider name must match the regex pattern `/^[a-z]+(-[a-z]+)*$/`
exactly.
### Base URL [#base-url]
* Must be a valid URL pointing to your provider's base endpoint
* On the hosted service the URL must be `https://` and must not resolve to a
private, reserved, or internal address. `http://` URLs and private-network
hosts (for example a service on your own network) work only on self-hosted
deployments that set `ALLOW_INSECURE_PROVIDER_URLS=true`
* LLMGateway will append `/v1/chat/completions` automatically — do not include `/v1` in the base URL (it is not stripped, so the path would be doubled)
* **Example**: `https://api.example.com` → `https://api.example.com/v1/chat/completions`
* If your provider serves chat completions at another path, enter the full endpoint ending in `/chat/completions` and LLMGateway uses it as-is
* **Example**: `https://api.example.com/v2/ai/openai/chat/completions` is called unchanged
### API Token [#api-token]
* Provider-specific authentication token
* Used in the `Authorization: Bearer {token}` header
Unlike built-in providers, custom provider models are not validated, giving
you complete flexibility.
## Supported Features [#supported-features]
Custom providers serve **chat completions** — `/v1/chat/completions` and the endpoints that proxy through it (`/v1/responses`, `/v1/messages`, and the AI SDK gateway endpoints under `/v*/ai`). Gateway features that apply to that path (caching, routing, IAM, logging, cost tracking via the custom model catalog) work as usual. Embeddings, images, audio, video, rerank, OCR, and moderations do not route to custom providers.
## Custom Model Catalog [#custom-model-catalog]
The custom model catalog is an **Enterprise** feature. Managing entries
requires an enterprise plan, but the gateway always honors catalog entries
that already exist.
By default, requests through a custom provider are **not billed** and have **no
enforced limits** — LLMGateway has no catalog entry for the model, so it cannot
know its pricing, context window, or capabilities. The **custom model catalog**
lets you define that information per custom provider key, so LLMGateway can
attribute cost, enforce limits, and report usage just like a built-in model.
Custom catalog models are **text-output** models. Multi-modal *input* (images,
audio) is still supported and can be priced via the input fields below; output
generation (images, video, audio) is intentionally out of scope because it is
too provider-specific to bill generically.
### Defining a model [#defining-a-model]
Open **Organization → Models**, pick the custom provider key, and add a
catalog entry. Every field except the model id is optional:
| Field | Purpose |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| **Model id** | The id used after the provider prefix (e.g. `gpt-5.5` in `mycompany/gpt-5.5`). |
| **Display name** | Optional human-readable label. |
| **Context size** | Maximum input window in tokens. Enforced per request. |
| **Max output** | Maximum completion tokens. Enforced against `max_tokens`. |
| **Token prices** | Input, output, cached input, cache read, cache write (5m and 1h), per-request, web search, image input, and audio input prices. |
| **Capabilities** | `streaming`, `vision`, `tools`, `reasoning`, `jsonOutput`, `audio`. |
| **Supported parameters** | Advisory list of accepted request parameters. |
Prices are in **USD per token** and accept either decimal (`0.000003`) or
exponent (`3.0e-6`) notation.
### Cost attribution [#cost-attribution]
When a request matches a catalog entry, LLMGateway bills it using the entry's
token prices and records the cost on the activity log. Without a matching entry
the request stays unbilled (zero/null cost).
A known model id (for example `gpt-5.5`) routed through a custom provider is
**not** billed at that model's public pricing — only the prices you define in
the catalog apply. Define the model in the catalog to attribute cost.
### Limit and capability enforcement [#limit-and-capability-enforcement]
When an entry defines limits or capabilities, LLMGateway enforces them before
forwarding the request:
* **Context size / max output** — requests that would exceed the configured
window or output budget are rejected with `400`.
* **Capabilities** — when a flag is explicitly set to disabled, requests that use
that feature are rejected (e.g. images against a non-`vision` model, tools
against a non-`tools` model, streaming against a non-streaming model). Flags
left unset stay permissive and the upstream provider enforces them.
### Restricting to the catalog [#restricting-to-the-catalog]
Each custom provider key has an **Only allow catalog models** switch. When
enabled, requests through that provider must reference a defined catalog model —
undefined models are rejected with `400`. This guarantees that every request has
known cost attribution and enforced limits. When disabled, undefined models
still work but remain unbilled, as before.
### Routing bare model ids [#routing-bare-model-ids]
A catalog entry whose model id equals a built-in model id and has a price joins
routing for that bare id: `gpt-5.5` can then be served by your custom provider
or a built-in provider, scored like any other candidate (see
[routing](https://docs.llmgateway.io/features/routing)). In hybrid mode, providers you hold a key for are
preferred. A provider-prefixed id always pins that provider.
Because the custom provider is one candidate among several:
* A failed attempt falls back to the next provider under the usual
[retry rules](https://docs.llmgateway.io/features/routing#what-triggers-a-retry). `4xx` responses are
returned as client errors.
* Its recent uptime is tracked per model. Below the low-uptime threshold
(default 90%) it is skipped while another provider can serve the request, and
retried a few minutes later.
The serving provider is returned in `metadata.used_provider`.
## Visibility [#visibility]
Every active organization member — including project-scoped **developer**
members — can browse the org's custom providers and their model catalogs
read-only on the **Models** page, so developers can always discover
which providers and models are available to them. Creating, editing, and
deleting providers, models, and attestations stays restricted to organization
owners and admins.
## Compliance attestation [#compliance-attestation]
If your organization enforces a [provider compliance policy](https://docs.llmgateway.io/features/compliance),
custom providers are blocked by default — they have no catalogue data policy, so
the fail-closed rules treat them as non-compliant. Organization owners and
admins can record a per-key **self-attestation** of the deployment's compliance
posture under **Models → Compliance attestation**, which the gateway then
evaluates against the policy exactly like a catalogue provider's data policy.
See [Compliance → Custom providers](https://docs.llmgateway.io/features/compliance#custom-providers) for
details and caveats.
# Data Retention
URL: https://docs.llmgateway.io/features/data-retention
LLM Gateway offers configurable data retention policies that allow you to store full request and response payloads. This enables powerful debugging capabilities, detailed analytics, and compliance with data governance requirements.
## Retention Levels [#retention-levels]
LLM Gateway supports two retention levels that can be configured per organization:
| Level | Description | Storage Cost |
| ------------------- | --------------------------------------------------------------------------------------------------------------- | --------------- |
| **Metadata Only** | Stores request metadata (timestamps, models, token counts, costs, latency) without full payloads. Default. | Free |
| **Retain All Data** | Stores complete request and response payloads including messages, tool calls, and attachments. Enterprise only. | $0.01/1M tokens |
Metadata-only retention is enabled by default and provides usage analytics
without additional storage costs.
**Retain All Data** is an [Enterprise](https://llmgateway.io/enterprise)
feature. New organizations default to Metadata Only and cannot enable Retain
All Data unless they are on Enterprise. Existing non-Enterprise organizations
that currently have Retain All Data enabled keep it until **November 8,
2026**, when the hosted platform (llmgateway.io) switches them to Metadata
Only automatically; they can switch to Metadata Only earlier themselves.
Self-hosted deployments are not switched automatically. Once off, Retain All
Data cannot be turned back on without Enterprise.
Retention levels are configurable on standard (pay-as-you-go) organizations
only. DevPass and chat subscriptions are always metadata only — their request
and response payloads are not retained, and there is no setting to turn
payload storage on. The Responses API exception below still applies to them.
## Storage Pricing [#storage-pricing]
When full data retention is enabled, storage is billed at **$0.01 per 1 million tokens**. This rate applies to:
* Input tokens (prompt)
* Cached input tokens
* Output tokens (completion)
* Reasoning tokens
Storage costs are calculated per request and billed separately from inference. When "Retain All Data" is enabled, each response's `usage.cost_details` object includes a `data_storage_cost` field with the per-request storage cost in USD. See [Cost Breakdown](https://docs.llmgateway.io/features/cost-breakdown) for the full list of cost fields.
### Example Cost Calculation [#example-cost-calculation]
For a request with:
* 1,000 input tokens
* 500 output tokens
* 1,500 total tokens
Storage cost = 1,500 / 1,000,000 × $0.01 = **$0.000015**
## Configuring Retention [#configuring-retention]
Data retention is configured at the organization level in your dashboard settings. The setting is only available on standard pay-as-you-go organizations, and enabling **Retain All Data** requires an Enterprise plan:
1. Navigate to **Organization Settings** → **Policies**
2. Select your preferred **Data Retention Level**
3. Save changes
Only organization **owners** can change the retention level — admins and project admins receive a `403`.
Changing retention settings applies to new requests only. Existing stored data
follows the retention period active when it was created.
## Retention Periods [#retention-periods]
Data is retained for 30 days for all users, regardless of plan. After the retention period expires, the stored payloads (prompts, completions, tool payloads, raw request and response bodies) are automatically cleared; request metadata used for analytics is kept. Payloads already stored by a non-Enterprise organization during the transition window follow the same 30-day period after it switches to Metadata Only.
The Responses API does not require data retention. Stored responses (used for
`previous_response_id` chaining and `GET /v1/responses/:id`) are kept in
dedicated storage for 30 days — matching OpenAI's own retention — regardless
of your organization's data retention policy, and are not billed as data
storage. Because this is independent of the retention level, it applies to
metadata-only organizations, including DevPass and chat, whose Responses API
input and output items are therefore held for up to 30 days. Send `store:
false` with the request to opt out. When an Enterprise organization's
[zero-data-retention policy](https://docs.llmgateway.io/features/compliance) is active, the gateway
rejects Responses API requests unless `store: false` is set, does not retain
compaction state, bypasses the gateway response cache, strips provider
prompt-cache markers, and prevents payload retention or project response
caching from being enabled until ZDR is disabled. ZDR cannot be enabled until
both settings are off.
## Accessing Stored Data [#accessing-stored-data]
When **Retain All Data** is enabled (Enterprise), you can access your stored requests through the dashboard:
* View request history with full payload inspection
* Filter by model and date range
* Inspect complete request and response payloads
## Use Cases [#use-cases]
### Debugging [#debugging]
Full data retention (Enterprise) enables you to:
* Inspect exact prompts sent to models
* Review complete responses including tool calls
* Trace conversation histories
* Identify issues in production
### Analytics [#analytics]
With stored payloads, you can:
* Analyze prompt patterns and effectiveness
* Track response quality over time
* Build custom dashboards and reports
* Measure model performance across use cases
### Compliance [#compliance]
Data retention helps meet compliance requirements by:
* Maintaining audit trails of AI interactions
* Enabling data governance policies
* Supporting incident investigation
* Providing records for regulatory requirements
## Billing Considerations [#billing-considerations]
### Credit Usage [#credit-usage]
In **API keys mode** (using your own provider keys):
* Only storage costs are deducted from LLM Gateway credits
* Inference costs are billed directly to your provider
In **credits mode**:
* Both inference and storage costs are deducted from credits
### Monitoring Storage Costs [#monitoring-storage-costs]
Storage costs appear in:
* Usage dashboard under "Storage" category
* Billing invoices as a separate line item
Enable [auto top-up](https://llmgateway.io/dashboard) in billing settings to
ensure uninterrupted service when storage costs accumulate.
## Self-Hosted Deployments [#self-hosted-deployments]
Self-hosted deployments have full control over data retention:
* Enable or disable the 30-day cleanup job with `ENABLE_DATA_RETENTION_CLEANUP=true` (disabled by default; the period itself is not configurable)
* Data is stored in your own PostgreSQL database
* No additional storage costs (you manage your own infrastructure)
## Privacy and Security [#privacy-and-security]
* All stored data is encrypted at rest
* Access is restricted to organization members with appropriate permissions
* Stored payloads are automatically cleared after the retention period (on self-hosted deployments, only when the cleanup job is enabled via `ENABLE_DATA_RETENTION_CLEANUP=true`)
* You can request immediate deletion of specific records through support
# Data Streams
URL: https://docs.llmgateway.io/features/data-streams
Data streams deliver your organization's logs to the tools your security and data teams already use, as signed HTTPS requests to an endpoint you run:
* **SIEM forwarding** sends [audit logs](https://docs.llmgateway.io/features/audit-logs): every admin action in the organization.
* **Log export** sends request log metadata: one event per gateway request with model, provider, tokens, cost, latency, and finish reason.
Data streams are an [**Enterprise**](https://llmgateway.io/enterprise) feature
enabled per organization. Contact us to turn them on; request log export is a
separate switch because each pass reads your request logs. Manage streams on
the [**Data Streams**](https://docs.llmgateway.io/learn/data-streams) page.
## What is sent [#what-is-sent]
Events only ever carry metadata. Prompts, completions, tool calls, and tool results are never exported, and there is no option to include them.
| Source | Event fields |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Audit logs** | `id`, `timestamp`, `organizationId`, `userId`, `action`, `resourceType`, `resourceId`, `metadata` (the same change summary shown on the audit log page) |
| **Request logs** | `id`, `timestamp`, `requestId`, `organizationId`, `projectId`, `apiKeyId`, models and providers, token counts, `cost`, latency, finish reasons, error and cache flags |
## Delivery [#delivery]
Each batch is a `POST` with a JSON body of `{ stream, source, events }` and these headers:
* `Content-Type: application/json`
* `X-LLMGateway-Signature: t=,v1=`, where `v1` is the HMAC-SHA256 of `.` with your signing secret, the same scheme as [platform webhooks](https://docs.llmgateway.io/features/embeddable-payments).
* `Authorization: Bearer ` when you set an optional bearer token.
Respond with any `2xx` status to accept the batch.
* Events are delivered in order, in batches of up to 500.
* A stream keeps a cursor and advances it only after your endpoint accepts a batch. A failed batch is retried, so delivery is at least once; deduplicate on the event `id`.
* Events are usually sent within 30 seconds. Delivery waits for database writes still in progress, so late writes are never skipped; while a long write runs, events can arrive a few minutes later.
* New streams start with events created after they were added. Use replay to backfill.
* Failed deliveries are retried with backoff, from 1 minute up to 1 hour. A stream pauses itself after 20 failed deliveries in a row, or after 3 deliveries your endpoint rejected with a `4xx` status since the last one it accepted. Owners and admins are notified; resume the stream once the endpoint is fixed and delivery continues from where it stopped.
* Delivery stops while the organization is not on the Enterprise plan or data streams are switched off for it.
## Replay [#replay]
**Replay** re-sends every event in a window of up to 30 days. It runs alongside live delivery and does not move the live cursor.
## Test [#test]
**Send test event** delivers one synthetic event (`"test": true`) and reports the endpoint's response.
All stream changes are recorded in audit logs as `data_stream.*` actions.
# Decisions
URL: https://docs.llmgateway.io/features/decisions
LLMGateway exposes an OpenAI-compatible `/v1/decisions` endpoint for decision
models: models that read text or images, answer questions you define, and
return typed answers with calibrated probabilities. There is no free-form text
in the response — every answer is a value your code can branch on directly.
Use it when a model call exists only to make a decision: routing a support
ticket, scoring a retrieved passage, checking a product photo for damage, or
classifying a record. A chat model can do these too, but you then have to parse
prose, and you get no probability to threshold on.
The request and response follow [OpenAI's Decisions API](https://developers.openai.com/api/docs/guides/decisions),
so the OpenAI SDKs work by pointing their base URL at LLMGateway. The same
request shape works for every decision model, whichever provider serves it.
For the full request and response schema, see the
[API reference](https://docs.llmgateway.io/v1_decisions).
## Models [#models]
| Model | Provider | Input | Price (input only) |
| ---------------------- | -------- | ------------ | ----------------------------------- |
| `gpt-6-luna-decisions` | OpenAI | Text, images | $0.10 / 1M tokens ($0.20 over 272K) |
| `microsoft-decision-1` | Azure | Text | $0.042 / 1M tokens |
| `jev-1.13.0` | TypeSafe | Text | $0.042 / 1M tokens |
`microsoft-decision-1` is served by Azure and works with LLMGateway credits.
To use your own Azure key instead, deploy Microsoft-Decision-1 in Microsoft
Foundry with the deployment name `Decision-1`, then add the Azure key and
resource in LLMGateway. The gateway calls the resource's Foundry host
(`https://.services.ai.azure.com`).
Browse all decision models on the
[models page](https://llmgateway.io/models?filters=1).
## Endpoint [#endpoint]
`POST https://api.llmgateway.io/v1/decisions`
## Question types [#question-types]
| Type | Ask | Answer |
| ----------- | ------------------------------------ | ---------------------------------------------------------------- |
| `predicate` | Whether a condition is true | `probability`: 0 to 1 |
| `choice` | One option from `choices` you define | `choice`, `probabilities` per option, `confidence` |
| `score` | A rating across ordered `levels` | `score` (can land between levels), `probabilities`, `confidence` |
Answers come back in the order you asked, echoing each question's `name`. A
question the model declines returns `{ "type": "refusal", "name": … }` while
the others are still answered.
## cURL [#curl]
```bash
curl -X POST "https://api.llmgateway.io/v1/decisions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-luna-decisions",
"input": "Help! My payouts have been failing for 3 days.",
"questions": [
{
"type": "choice",
"name": "department",
"instructions": "Which team should handle this?",
"choices": [
{ "value": "billing", "description": "Payments, invoicing, refunds" },
{ "value": "technical", "description": "Bugs, outages, integrations" }
]
},
{
"type": "predicate",
"name": "is_urgent",
"instructions": "Does this convey urgency?"
},
{
"type": "score",
"name": "impact",
"instructions": "Rate the operational impact.",
"levels": [{ "label": "None" }, { "label": "Limited" }, { "label": "Critical" }]
}
]
}'
```
```json
{
"model": "openai/gpt-6-luna-decisions",
"answers": [
{
"type": "choice",
"name": "department",
"choice": "billing",
"probabilities": [
{ "value": "billing", "probability": 0.88 },
{ "value": "technical", "probability": 0.12 }
],
"confidence": 0.81
},
{ "type": "predicate", "name": "is_urgent", "probability": 0.95 },
{
"type": "score",
"name": "impact",
"score": 1.9,
"probabilities": [
{ "value": 0, "label": "None", "probability": 0.0 },
{ "value": 1, "label": "Limited", "probability": 0.1 },
{ "value": 2, "label": "Critical", "probability": 0.9 }
],
"confidence": 0.88
}
],
"usage": { "input_tokens": 318, "output_tokens": 0, "total_tokens": 318 }
}
```
## OpenAI SDK [#openai-sdk]
Point the client at LLMGateway and call `decisions.create` (OpenAI JavaScript
SDK 7.30.0 or later, Python SDK 3.26.0 or later):
```ts
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.llmgateway.io/v1",
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const decision = await client.decisions.create({
model: "gpt-6-luna-decisions",
input: ticket,
questions: [
{
type: "predicate",
name: "is_urgent",
instructions: "Does this convey urgency?",
},
],
});
const [answer] = decision.answers;
if (answer.type === "predicate" && answer.probability > 0.8) {
await escalate(ticket);
}
```
## Images [#images]
Send user messages with `input_text` and `input_image` parts to evaluate images
on models that accept them:
```json
{
"model": "gpt-6-luna-decisions",
"input": [
{
"role": "user",
"content": [
{ "type": "input_text", "text": "Inspect the product in this photo." },
{ "type": "input_image", "image_url": "data:image/png;base64,…" }
]
}
],
"questions": [
{
"type": "predicate",
"name": "visible_damage",
"instructions": "Does the product have visible damage?"
}
]
}
```
Text-only models such as `jev-1.13.0` and `microsoft-decision-1` reject image
parts with a 400.
Decision models are billed on input tokens only — the input plus every
question. There is no output, cache-read or cache-write charge.
Decision models only work on `/v1/decisions` (TypeSafe models also on the
legacy `/v1/systemone`). Requesting one on `/v1/chat/completions` returns a
400 pointing you at the right endpoint, and they cannot generate text or call
tools. There is no streaming — the response is a single object.
## Migrating from /v1/systemone [#migrating-from-v1systemone]
`/v1/systemone` is TypeSafe's request format and serves Jev only. It will be
deprecated in favor of `/v1/decisions`, and new decision models ship on
`/v1/decisions` only. Jev works on both, at the same price, so you can move
without changing models.
| `/v1/systemone` | `/v1/decisions` |
| --------------------------------- | ------------------------------------------------------- |
| `state` (string, object or array) | `input` (string, or user messages with text and images) |
| `questions` map keyed by id | `questions` array; the id goes in `name` |
| `noul` question | `predicate` question |
| `choice` `criteria` record | `choices`: `[{ value, description }]` |
| `score` `criteria` list | `levels`: `[{ label, description }]` |
| `answers` map keyed by id | `answers` array in question order, with `name` |
| `noul` answer | `probability` |
| `probabilities` record | `probabilities` array of `{ value, probability }` |
| `legend` on score answers | `label` on each score probability |
Structured state has no direct equivalent: serialize it to a string (for
example with `JSON.stringify`) and pass it as `input`.
# Document Reading
URL: https://docs.llmgateway.io/features/documents
LLMGateway supports sending documents (PDFs and other file types) to document-capable models using OpenAI's `file` content block format. The gateway forwards the document to the underlying provider so the model can read and reason over its contents.
## Document-Capable Models [#document-capable-models]
Document input is currently supported on Google Gemini models via Google AI Studio and Google Vertex AI. You can find document-capable models on the [models page with the document filter](https://llmgateway.io/models?filters=1\&document=true).
## Sending a Document [#sending-a-document]
Add a `file` content block to a user message. The `file_data` field must be a base64-encoded data URL that includes the document's MIME type.
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.6-flash",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Summarize this document."
},
{
"type": "file",
"file": {
"filename": "report.pdf",
"file_data": "data:application/pdf;base64,JVBERi0xLjQKJ..."
}
}
]
}
]
}'
```
### Content Block Fields [#content-block-fields]
* **`type`**: must be `"file"`.
* **`file.filename`** *(optional)*: original filename, shown in Lounge and forwarded for context.
* **`file.file_data`**: base64-encoded data URL of the form `data:;base64,`.
The `file.file_id` field (for referencing files uploaded via a provider's
Files API) is accepted by the schema but not currently supported by the Google
transform. Use `file_data` with an inline base64 data URL.
## Supported File Types [#supported-file-types]
The accepted MIME types depend on the target model. Gemini models commonly support:
* `application/pdf`
* `text/plain`
* `text/html`
* `text/css`
* `text/javascript`
* `text/csv`
* `text/markdown`
* `text/xml`
If the upstream provider rejects the MIME type, the gateway surfaces a `400` error including the unsupported MIME type and the provider it was sent to. To use a different file type, encode the file with the matching MIME type in the data URL prefix.
## Encoding a File as a Data URL [#encoding-a-file-as-a-data-url]
Any tool that can produce base64 output works. For example, in a shell:
```bash
DATA=$(base64 -i report.pdf | tr -d '\n')
echo "data:application/pdf;base64,$DATA"
```
Or in JavaScript:
```javascript
import { readFileSync } from "node:fs";
const buffer = readFileSync("report.pdf");
const fileData = `data:application/pdf;base64,${buffer.toString("base64")}`;
```
Then pass `fileData` as the `file.file_data` value in your request.
## Multiple Documents [#multiple-documents]
You can include multiple `file` blocks in a single message, optionally mixed with text and image content:
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.1-pro-preview",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "Compare these two reports." },
{
"type": "file",
"file": {
"filename": "q1.pdf",
"file_data": "data:application/pdf;base64,JVBERi0x..."
}
},
{
"type": "file",
"file": {
"filename": "q2.pdf",
"file_data": "data:application/pdf;base64,JVBERi0x..."
}
}
]
}
]
}'
```
## Error Handling [#error-handling]
The gateway returns `400` for the following document-related errors:
* The selected model does not support document input.
* The `file` block is missing both `file_data` and `file_id`.
* `file_data` is not a valid base64 data URL.
* The upstream provider rejects the document's MIME type for the selected model.
# Dynamic Routes
URL: https://docs.llmgateway.io/features/dynamic-routes
Dynamic routes let you move routing logic out of your application code and into the gateway. Instead of hardcoding a model, you define a named decision graph — conditions on the request, classifier verdicts, percentage-based traffic splits, and model targets — and invoke it by putting `dynamic/` in the `model` field of a chat request. Dynamic routes are resolved on `/v1/chat/completions` and the endpoints that proxy through it (`/v1/responses`, `/v1/messages`, and the AI SDK gateway endpoints under `/v*/ai`) — not on embeddings, images, audio, video, rerank, OCR, or moderations:
```bash
curl https://api.llmgateway.io/v1/chat/completions \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "x-user-tier: paid" \
-d '{
"model": "dynamic/support",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
The graph is evaluated on every request. Once it resolves to a model, the request flows through the same [smart routing](https://docs.llmgateway.io/features/routing) as any other request: weighted provider scoring, sticky sessions, and automatic cross-provider fallback all apply to the resolved target.
Dynamic routes are available on the Enterprise plan. Manage them under{" "}
Project Settings → Dynamic Routes in the dashboard, either in a visual
drag-and-drop editor or as raw JSON.
## The route graph [#the-route-graph]
A route is a JSON document with an `entry` node id and a list of nodes. Evaluation starts at `entry` and follows branches until it reaches a `model` node (route the request) or an `end` node (reject the request with a `400`).
```json
{
"entry": "tier",
"nodes": [
{
"id": "tier",
"type": "conditional",
"conditions": [
{
"field": { "source": "header", "path": "x-user-tier" },
"op": "eq",
"value": "paid",
"next": "premium"
}
],
"else": "split"
},
{ "id": "premium", "type": "model", "model": "claude-sonnet-4-6" },
{
"id": "split",
"type": "percentage",
"splits": [
{ "weight": 80, "next": "stable" },
{ "weight": 20, "next": "experiment" }
]
},
{
"id": "stable",
"type": "model",
"model": "gpt-5-nano",
"providers": ["openai"]
},
{ "id": "experiment", "type": "model", "model": "gemini-2.5-flash" }
]
}
```
Node ids may contain letters, digits, hyphens, and underscores.
## Node types [#node-types]
### `conditional` [#conditional]
Evaluates its conditions top to bottom; the first match wins and the request follows that condition's `next`. When nothing matches, the request follows `else`.
Each condition reads one field from the request:
| `field.source` | What `field.path` means |
| -------------- | --------------------------------------------------------------------------------- |
| `header` | A request header name (case-insensitive), e.g. `x-user-tier` |
| `body` | A dot-path into the JSON request body, e.g. `metadata.segment` or `max_tokens` |
| `metadata` | A gateway-provided request attribute: `orgId`, `projectId`, `apiKeyId`, or `plan` |
Supported operators:
| Operator | Matches when | Value type |
| ---------- | ------------------------------------------------------------------------- | ------------------------- |
| `eq` | The field equals the value (compared as strings) | string / number / boolean |
| `neq` | The field differs from the value — also matches when the field is missing | string / number / boolean |
| `in` | The field equals any entry in the value array | array of strings |
| `contains` | The field's string form contains the value | string |
| `gt`, `lt` | The field is numerically greater / less than the value | number |
| `exists` | The field is present (no value) | — |
### `classifier` [#classifier]
Rates the request with a classifier and branches on one of its answers. Set `kind` to the classifier to use (currently `jev`, TypeSafe's decision model) and `on` to the answer to branch on:
| `on` | Values |
| ------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `difficulty` | `low`, `medium`, `high` |
| `task` | `coding`, `math`, `analysis`, `writing`, `extraction`, `summarization`, `translation`, `conversation`, `agentic`, `other` |
| `outputType` | `short_answer`, `long_form`, `code`, `structured_data` |
```json
{
"id": "rate",
"type": "classifier",
"kind": "jev",
"on": "difficulty",
"cases": [
{ "value": "high", "next": "frontier" },
{ "value": "low", "next": "cheap" }
],
"else": "balanced"
}
```
The node adds one short round trip before the upstream call, and only routes that contain a `classifier` node pay for it. Each rating is billed at the catalogue rate for [`jev-1.13.0`](https://llmgateway.io/models/jev-1.13.0/typesafe) — roughly $0.0001 per call — and recorded as its own log entry against the same project and API key as the request that triggered it; see [what the classifier costs](https://docs.llmgateway.io/features/routing#what-it-costs). A [sticky session](https://docs.llmgateway.io/features/routing#sticky-session-routing) is rated once and the rest of the conversation reuses that verdict, so a multi-turn chat is not re-rated per turn.
`else` covers both outcomes you need it to: no case matched, **and** no verdict was produced. The classifier is fail-open — an outage, a timeout, a missing credential, or a compliance policy that blocks its provider all take the `else` branch rather than failing the route. Give `else` a sensible default model rather than an `end` node.
The verdict is recorded on the request log under `routingMetadata.dynamicRoute.classifier`, so you can see which branch was taken and why.
### `percentage` [#percentage]
Splits traffic across branches by relative weight. The draw is **deterministic per session**: the split key is the request's session id (`x-session-id` and the other [sticky session routing](https://docs.llmgateway.io/features/routing#sticky-session-routing) signals), so a conversation keeps its assignment across requests instead of flip-flopping between experiment arms. Requests without a session id get an independent draw per request.
Weights are relative — `{ 80, 20 }` and `{ 4, 1 }` produce the same split.
### `model` [#model]
Terminates evaluation and routes the request to a catalog model (see the [models page](https://llmgateway.io/models) for available ids). Optional `providers` restricts routing to those providers in preference order for initial selection and, on non-sticky requests, cross-provider fallback. Eligibility checks and hybrid-mode key preference apply first. A healthy session pin takes precedence; when a pin becomes unavailable, the next request chooses its replacement using the order. Sticky requests still skip cross-provider retries. Scores choose among regions of the same provider, and explicit provider order disables exploration. When `providers` is omitted, every eligible provider serving the model is a candidate and weighted smart routing picks the best one.
A `model` node can also target one of your organization's [custom catalog models](https://docs.llmgateway.io/features/custom-providers#custom-model-catalog) as `/`. Such a node must not set `providers` (the custom model already fixes its provider), and the referenced custom model must be active in your organization when the route is saved or published.
### `end` [#end]
Terminates evaluation and rejects the request with a `400`. Useful as an explicit deny branch — for example, refusing traffic that doesn't carry a required header.
## Validation [#validation]
Graphs are validated when you save a draft and again when you publish:
* every referenced node must exist and be reachable from `entry`
* cycles are rejected — evaluation is deterministic per request, so a revisited node would loop forever
* model ids and provider ids must exist in the catalog, and each listed provider must actually serve the model
* operator/value mismatches (e.g. `gt` with a non-numeric value) are rejected
* a `classifier` node's case values must be valid for its `on` answer, and must not repeat
An invalid graph can never be saved or published, and publishing re-validates against the live model catalog so a stale draft can't resurrect a removed model.
## Versions and rollback [#versions-and-rollback]
Edits always go to the route's **draft**. Publishing snapshots the draft as an immutable, numbered version and points the route at it; the published version is what serves traffic. Rolling back re-points the route at any previous version instantly — no data migration, no re-deploy.
A route only serves requests when it is **enabled** and has a **published version**; otherwise requests using it are rejected with a `404`.
## Observability [#observability]
Each request served through a dynamic route records the route name, published version, and the node path the evaluation took in its routing metadata, alongside the usual provider scoring details — visible in the activity log detail view.
The project's **Analytics** page compares each route's spend with sending every request to the priciest model in the route; see [routing savings](https://docs.llmgateway.io/features/routing#routing-savings).
## Reliability [#reliability]
Published routes are cached in the gateway and carry a stale fallback: when the database is temporarily unreachable, recently used routes keep resolving from cache, the same way API keys and project settings do.
# Embeddable Payments
URL: https://docs.llmgateway.io/features/embeddable-payments
The **Payments SDK** lets you drop **end-user payments + in-app credit purchases** into your product the same way Stripe Elements lets you drop in payments. Your end-users get their **own wallet**, buy credits **inside your app**, and pay per request for any model the gateway supports. LLM Gateway is the merchant of record; you set a markup and keep the margin.
**Preview — opt-in only.** Embeddable Payments is in preview and enabled on an
opt-in basis per project. The **Settings → Payments SDK** dashboard page shows
a read-only preview until the feature is turned on for your project. [Contact
us](mailto:contact@llmgateway.io) to request access before integrating.
## When to use this [#when-to-use-this]
Reach for the Payments SDK when **you embed LLM Gateway's billing on your own site** and want **your end-users to pay for the AI they use**, each with their own wallet and credit balance. It handles end-user **payments and sessions**: per-user wallets, in-app credit top-ups (Stripe), short-lived browser sessions scoped to a single wallet, and developer-margin payouts.
**This is a payments product, not an AI client SDK.** Do not confuse it with a
normal AI SDK such as the OpenAI SDK or the Vercel AI SDK. If you just want to
call models from your own backend with a single API key, use the
[OpenAI-compatible API](https://docs.llmgateway.io/quick-start) instead — you do not need the Payments
SDK. Use the Payments SDK only when you need to **charge your own end-users**
and give each of them a wallet and checkout inside your app.
It ships as three packages:
| Package | Runs in | Use it for |
| ---------------------- | ------------------------- | ------------------------------------------------------------------------------------ |
| `@llmgateway/server` | Your backend (secret key) | Mint end-user sessions, manage wallets/customers, verify webhooks, trigger payouts |
| `@llmgateway/client` | Browser (headless) | Framework-agnostic chat/image/embeddings + balance/top-up, with auto session refresh |
| `@llmgateway/elements` | React | Drop-in ``, ``, `` + hooks |
A complete, runnable Next.js example lives in the templates repo:
[**Embeddable Payments
template**](https://github.com/theopenco/llmgateway-templates/tree/main/templates/embeddable-credits).
## How it works [#how-it-works]
```
Your backend ──(secret key sk_)──▶ POST /v1/sessions ──▶ ephemeral session token (es_, ~15 min)
│ │
└────────── returns es_ to your frontend ◀────────────────┘
│
Browser (es_) ──▶ chat / images / embeddings ──▶ debits the end-user wallet
└──▶ buy credits (Stripe Elements) ─▶ credits land in the wallet
```
* Your **secret key** (`sk_…`) never leaves your backend. It mints short-lived **ephemeral session tokens** (`es_…`) scoped to one end-user wallet.
* The **browser** only ever holds the `es_…` token (and a publishable Stripe key). It calls the gateway directly; usage is billed to that user's wallet.
* **Markup is applied at top-up time**: if you set a 20% markup and a user buys $10, their wallet is credited the net spend power and your **margin accrues to your organization** for later payout.
* **Top-up bonus (optional)**: set a bonus percent to credit end-users *more* than they pay — e.g. a 50% bonus turns a $10 top-up into $15 of spend power. The extra credits are funded from **your organization's credit balance** at top-up time (capped at your available credits), so it's a promotional lever you can switch on or off anytime. Markup and bonus are independent: the bonus is applied on top of the net credited amount.
## Set up in the dashboard [#set-up-in-the-dashboard]
Before you write any code, configure the project you want to embed:
1. Open the LLM Gateway dashboard and select your project.
2. Go to **Settings → Payments SDK** and turn on **End-user sessions** (this requires the preview to be enabled for your project).
3. *(Optional)* Set a **markup percent** — the margin you earn on every top-up — and/or a **top-up bonus percent** to gift end-users extra credit (funded from your organization's credit balance).
4. Add the browser origins allowed to call the gateway, one per line (e.g. `https://app.example.com`).
5. Under **Receipts & branding**, set your **brand name**, **support email** and **statement descriptor** so end-user payments are recognisable as yours (see below), then click **Save Settings**.
6. Under **Platform Secret Keys**, click **Create Live Key** (or **Create Test Key**) and copy the `sk_…` value immediately.
7. Store it as a server-side environment variable, for example `LLMGATEWAY_SECRET_KEY`.
The platform secret key (`sk_…`) is different from a regular gateway API key (`llmgtwy_…`): it mints end-user sessions and must only ever be used from your backend.
## Receipts and statement descriptors [#receipts-and-statement-descriptors]
LLM Gateway is the **merchant of record** for every end-user top-up. Your
end-users are charged by us, and the receipt and card statement both carry our
details — which is confusing for someone who only ever visited *your* product.
The branding fields exist to close that gap.
After a successful top-up we email the end-user a receipt with a PDF attached:
* Your **brand name** leads the email and appears on the PDF as the product the
payment was for. It defaults to the project name.
* Your **support email**, when set, is the first contact address on the receipt.
Ours appears below it, since we are the merchant of record and have to stay
reachable for billing questions.
* Your **statement descriptor** is appended to our prefix on the cardholder's
statement, giving `LLMGTWY* ACME AI` rather than a bare `LLMGTWY`. Card
networks cap the whole string at 22 characters, so the suffix you supply is
limited to 13 and normalized on save.
The receipt also states that LLM Gateway processed the payment and what the
charge is called on the statement. That explanation appears **only** on these
receipts — a direct LLM Gateway purchase has no third party to explain, so it
gets a plain receipt instead.
Refunds send a credit note the same way.
We can only send a receipt to an address we have. Mint sessions with
`customer: { externalId: "user_123", email: "user@example.com" }` — a bare
`customer: "user_123"` string leaves the end customer without an email and no
receipt is sent. Passing the email on a later session updates the stored
record.
Test-mode wallets never receive receipts, matching Stripe's own behaviour of
not emailing receipts for sandbox payments.
**Test mode.** A `sk_test_…` key is a sandbox key: end-user wallet top-ups go
through Stripe's sandbox (use Stripe [test cards](https://docs.stripe.com/testing),
no real charges), and its wallets are fully segregated from live ones — the same
end-user gets independent test and live wallets. To keep sandbox money from
buying real inference, **test-mode wallets can only call free models**: use the
`auto` route (it picks a free model automatically) or a free model id; paid
models return a `403`. Pair a test secret key on your backend with
`mode="test"` on `` (see below) — the two must match.
Test-mode top-ups never receive the developer-funded top-up bonus, and a
`sk_test_…` key cannot manage webhook endpoints or use Stripe Connect
onboarding/payouts (both return `403` — use a live secret key).
The platform secret key is shown only once. Do not put it in frontend code,
browser bundles, mobile apps, or public repos. Only a keyed HMAC-SHA-256
fingerprint and masked preview are stored, so create a replacement if you lose
the secret.
## 1. Install [#1-install]
```bash
# backend
npm install @llmgateway/server
# frontend (pick one)
npm install @llmgateway/elements # React drop-in components
npm install @llmgateway/client # headless / non-React
```
## 2. Mint a session on your backend [#2-mint-a-session-on-your-backend]
Identify your signed-in user and mint a session bound to their wallet. Scope which models they may call.
```ts
// app/api/llmgateway/session/route.ts (Next.js Route Handler)
import { LLMGateway } from "@llmgateway/server";
const lg = new LLMGateway({ secretKey: process.env.LLMGATEWAY_SECRET_KEY! });
export async function POST() {
const session = await lg.sessions.create({
customer: { externalId: "user_123" }, // your stable user id
scope: { models: ["openai/gpt-4o-mini"] }, // lock down what they can call
ttlSeconds: 900, // optional, default 15 min
});
return Response.json(session); // { sessionToken, walletId, endCustomerId, expiresAt, publishableKey }
// publishableKey is null unless the project has an active publishable
// browser key — the browser authenticates with the sessionToken (es_…).
}
```
Always mint sessions server-side. Never ship your `sk_…` secret key to the
browser.
## 3a. Drop in the React components [#3a-drop-in-the-react-components]
Wrap your UI in `` and use the components. `fetchSession` is how the client refreshes the short-lived token before it expires.
```tsx
"use client";
import {
LLMGatewayProvider,
Chat,
CreditBalance,
BuyCredits,
} from "@llmgateway/elements";
const fetchSession = () =>
fetch("/api/llmgateway/session", { method: "POST" }).then((r) => r.json());
export default function Assistant({ session }) {
return (
);
}
```
Need full control over rendering? Use the hooks instead of the components:
* `useBalance()` → `{ balance, currency, recentLedger, loading, error, refetch, refetchUntilChange }`
* `useChat({ model })` → `{ turns, send, streaming, ... }`
`useBalance().refetchUntilChange()` polls until the balance actually changes —
use it after a purchase, since the wallet is credited asynchronously once the
Stripe webhook lands.
## 3b. Or go headless (any framework) [#3b-or-go-headless-any-framework]
```ts
import { LLMGatewayClient } from "@llmgateway/client";
const client = new LLMGatewayClient({
session: { token: session.sessionToken, expiresAt: session.expiresAt },
refresh: fetchSession, // auto-refreshes ~60s before expiry
});
// stream a completion (billed to the user's wallet)
for await (const delta of client.stream({
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "Hello!" }],
})) {
process.stdout.write(delta);
}
const { balance } = await client.getBalance();
```
The headless client also exposes `chat()`, `image()`, `embeddings()`, `getBalance()`, `createTopUp(amount)`, and `getConfig()`.
### Spend limits [#spend-limits]
`getBalance()` (and `useBalance()`) also returns a `limits` object describing the spend limits enforced on the session, the amount consumed so far, and — when a windowed limit is configured — when it resets. This lets you show the user how much of their allowance is left before a request is rejected.
```ts
const { balance, limits } = await client.getBalance();
// limits: {
// usageLimit, // lifetime spend cap (null = uncapped)
// usage, // consumed over the session's lifetime
// periodUsageLimit, // per-window cap (null = no windowed limit)
// periodUsageDurationValue, // e.g. 1
// periodUsageDurationUnit, // "hour" | "day" | "week" | "month"
// currentPeriodUsage, // consumed in the current window
// currentPeriodStartedAt, // ISO timestamp, or null
// currentPeriodResetAt, // ISO timestamp the window resets, or null
// }
```
`null` limit fields mean that cap is not configured. `currentPeriodUsage` is `"0"` and `currentPeriodResetAt` is `null` until the first spend in a fresh window.
## Buying credits [#buying-credits]
`` creates a Stripe PaymentIntent scoped to the user's wallet, renders Stripe's `PaymentElement`, and confirms the payment. Once LLM Gateway's webhook processes it, the wallet is credited the **net** amount (after your markup) and your margin accrues to your organization. If you've configured a **top-up bonus**, the additional developer-funded credit is applied on top of the net amount at the same time (debited from your organization's credit balance). The top-up API response includes `netCredited` and `bonusCredited` (the boosted total is their sum), and the `wallet.credited` webhook additionally carries `totalCredited`.
`@llmgateway/elements` bundles LLM Gateway's browser-safe Stripe publishable keys. Pass `mode="test"` to `` while developing to use Stripe test mode; omit it or pass `mode="prod"` for live payments (`"prod"` is the default). You never need to provide LLM Gateway's Stripe publishable key yourself, and the end-user never sees your `sk_…` secret key.
The frontend `mode` prop and the backend secret key must match. A `sk_test_…`
key creates the top-up PaymentIntent in the Stripe sandbox, which only the
`mode="test"` publishable key can confirm — mixing a test key with `mode="prod"`
(or vice versa) makes `` fail to confirm.
## Managing wallets & customers (server-side) [#managing-wallets--customers-server-side]
```ts
// grant credits directly (e.g. free trial)
await lg.wallets.credit({ walletId, amount: 5, reason: "Signup bonus" });
const wallet = await lg.wallets.retrieve(walletId);
// analytics: customers with balances + lifetime spend
const { customers } = await lg.customers.list();
const detail = await lg.customers.retrieve(endCustomerId);
```
## Webhooks [#webhooks]
Register an endpoint to react to wallet events. Events are signed (`X-LLMGateway-Signature`); verify them like Stripe.
```ts
await lg.webhookEndpoints.create({
url: "https://yourapp.com/webhooks/llmgateway",
enabledEvents: ["wallet.credited", "wallet.low_balance"],
});
// in your handler
const event = lg.webhooks.constructEvent(
rawBody,
signatureHeader,
endpointSecret,
);
```
Webhook URLs must be **https** and public — requests to private/internal
addresses are rejected (SSRF protection), both at registration and at delivery
time.
## Margin payouts (Stripe Connect) [#margin-payouts-stripe-connect]
Your accrued markup is held as a margin balance. Onboard a connected account and pay it out:
```ts
const { url } = await lg.connect.createOnboardingLink({
refreshUrl: "https://yourapp.com/settings/payouts",
returnUrl: "https://yourapp.com/settings/payouts?done=1",
});
// redirect the developer to `url`, then later:
const status = await lg.connect.status(); // { onboarded, payoutsEnabled, marginBalance }
const payout = await lg.connect.payout(); // transfer the accrued margin out
```
## Security model [#security-model]
* **Ephemeral tokens** (`es_…`) are short-lived and revocable; mint them per-user from your backend. Platform secrets and ephemeral tokens are stored only as keyed fingerprints, never as retrievable plaintext.
* **Model scopes** restrict each session to an allow-list of models.
* **Origin allowlist** (configured on the project) blocks browser calls from unexpected origins.
* **Per-session spend caps** (`scope.maxSpend`) bound how much a single session can spend.
## Full example [#full-example]
The end-to-end Next.js app — backend session route, provider, chat, and buy-credits — is in the templates repo:
➡️ [**Embeddable Payments template**](https://github.com/theopenco/llmgateway-templates/tree/main/templates/embeddable-credits)
# Embeddings
URL: https://docs.llmgateway.io/features/embeddings
LLMGateway exposes an OpenAI-compatible `/v1/embeddings` endpoint for generating vector representations of text — useful for semantic search, clustering, recommendations, and RAG.
Browse available embedding models on the [models page](https://llmgateway.io/models?filters=1\&embedding=true).
## Supported providers [#supported-providers]
* **OpenAI** — `text-embedding-3-small`, `text-embedding-3-large`, `text-embedding-ada-002`
* **Google AI Studio** — `gemini-embedding-2` (recommended), `gemini-embedding-001` (legacy)
* **Google Vertex AI** — `gemini-embedding-001`, `text-embedding-005`
The gateway translates between provider-native request/response shapes (e.g. Google's `:embedContent` / `:batchEmbedContents`) and the OpenAI-compatible payload, so you can swap models without changing your client code.
## cURL [#curl]
```bash
curl -X POST "https://api.llmgateway.io/v1/embeddings" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "text-embedding-3-small",
"input": "The quick brown fox jumps over the lazy dog."
}'
```
## OpenAI JS SDK [#openai-js-sdk]
```ts
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LLM_GATEWAY_API_KEY,
baseURL: "https://api.llmgateway.io/v1",
});
const response = await client.embeddings.create({
model: "text-embedding-3-small",
input: "The quick brown fox jumps over the lazy dog.",
});
console.log(response.data[0].embedding);
```
Embedding models are billed only for input tokens. There are no output tokens
since embeddings are fixed-size vectors.
## Improving retrieval quality [#improving-retrieval-quality]
Vector similarity is fast but approximate. For RAG, pair embeddings with
[rerank](https://docs.llmgateway.io/features/rerank): use embeddings to pull a broad candidate set, then
rerank those candidates to pick the few documents that actually go in the
prompt.
# Guardrails
URL: https://docs.llmgateway.io/features/guardrails
Guardrails protect your organization by automatically detecting and blocking harmful content in LLM requests before they reach the model.
Guardrails are available on the [**Enterprise
plan**](https://llmgateway.io/enterprise).
## Overview [#overview]
Guardrails run on every API request, scanning message content for:
* Security threats (prompt injection, jailbreak attempts)
* Sensitive data (PII, secrets, credentials)
* Policy violations (blocked terms, restricted topics)
When a violation is detected, you control what happens: block the request, redact the content, or log a warning.
## Organization and Project Scopes [#organization-and-project-scopes]
Guardrails are configured for the whole organization by default, and every project uses that configuration.
A project can opt out and define its own guardrails instead — useful when one application needs stricter or looser rules than the rest of the organization. Turn off **Use organization guardrails** in the project's Guardrails settings, and that project's configuration and custom rules fully replace the organization's for its traffic.
Exactly one scope is ever in force for a request:
| Project setting | What runs on that project's requests |
| ----------------------------------- | --------------------------------------------------------- |
| **Use organization guardrails** on | The organization configuration and its custom rules |
| **Use organization guardrails** off | The project's own configuration and its custom rules only |
Opting out never inherits partially: the organization's custom rules stop applying to that project too, and the project starts with a copy of the organization's current settings so nothing is silently dropped at the moment you opt out. Other projects are unaffected either way.
Both pages state which scope wins. A project page names itself and, while it inherits, shows the organization settings read-only; the organization page lists any projects that override it, so it is clear those settings apply everywhere else.
Only organization owners and admins can change guardrails, at either scope.
## System Rules [#system-rules]
Built-in rules protect against common threats:
### Prompt Injection Detection [#prompt-injection-detection]
Detects attempts to override or manipulate system instructions. Common patterns include:
* "Ignore all previous instructions"
* "You are now a different AI"
* Hidden instructions in encoded text
### Jailbreak Detection [#jailbreak-detection]
Identifies attempts to bypass safety measures:
* DAN (Do Anything Now) prompts
* Roleplay-based bypasses
* Instruction override attempts
### PII Detection [#pii-detection]
Identifies personal information:
* Email addresses
* Phone numbers
* Social Security Numbers
* Credit card numbers
* IP addresses
* Passport and driver's license numbers
When the action is set to **redact**, PII is replaced with placeholders like `[EMAIL_REDACTED]`.
Detection is validated rather than purely pattern-based, so identifiers that only
look like PII are left alone: card numbers must pass the Luhn checksum, phone
numbers need grouping separators or a nearby phone keyword, and passport and
license numbers need a nearby keyword. A bare numeric id such as a product id,
order number or unix timestamp is therefore never redacted as a phone number.
### Secrets Detection [#secrets-detection]
Detects credentials and API keys:
* AWS access keys and secrets
* Generic API keys
* Passwords in common formats
* Private keys
High-entropy checks and placeholder filtering keep ordinary content out of the
way: git SHAs and hex digests are not treated as AWS secrets, and template
values such as `YOUR_API_KEY`, `${OPENAI_API_KEY}` or `********` are not treated
as credentials.
### File Type Restrictions [#file-type-restrictions]
Control which file types can be uploaded:
* Configure allowed MIME types
* Set maximum file size limits
* Block potentially dangerous file types
### Document Leakage Prevention [#document-leakage-prevention]
Detects attempts to extract confidential documents or internal data.
## Configurable Actions [#configurable-actions]
For each rule, choose how to respond:
| Action | Behavior |
| ---------- | --------------------------------------------------- |
| **Block** | Reject the request with a content policy error |
| **Redact** | Remove or mask the sensitive content, then continue |
| **Warn** | Log the violation but allow the request to proceed |
## Custom Rules [#custom-rules]
Create custom rules for your use case, on the organization or on a single project:
### Blocked Terms [#blocked-terms]
Prevent specific words or phrases from being used:
* Match type: exact, contains, or regex
* Case-sensitive matching option
* Multiple terms per rule
### Custom Regex [#custom-regex]
Match patterns unique to your organization:
* Internal project codenames
* Customer identifiers
* Domain-specific sensitive data
### Topic Restrictions [#topic-restrictions]
Block content related to specific topics:
* Define restricted topics
* Keyword-based detection
## Security Events Dashboard [#security-events-dashboard]
Monitor all guardrail violations with a dedicated dashboard:
* **Total violations** — Overall count and trends
* **By action** — Breakdown of blocked, redacted, and warned
* **By category** — Which rules are being triggered
* **Detailed logs** — Individual violations with timestamps and matched patterns
## How It Works [#how-it-works]
```
Request → Guardrails Check → Action Based on Rules → Forward to Model (if allowed)
↓
Log Violation
```
1. **Request received** — API request comes in with messages
2. **Scope resolved** — The project's own guardrails if it overrides, otherwise the organization's
3. **Content scanned** — All text content is checked against enabled rules
4. **Violations detected** — Matches are identified and logged
5. **Action taken** — Based on rule configuration (block/redact/warn)
6. **Request proceeds** — If not blocked, the (potentially redacted) request continues
## Best Practices [#best-practices]
1. **Start with warnings** — Enable rules in warn mode first to understand your traffic patterns
2. **Review violations** — Check the Security Events dashboard regularly
3. **Tune custom rules** — Adjust blocked terms and regex patterns based on false positives
4. **Layer defenses** — Use multiple rule types together for comprehensive protection
## Get Started [#get-started]
Guardrails are an Enterprise feature. [Contact us](https://llmgateway.io/enterprise) to enable Enterprise for your organization.
# Image Generation
URL: https://docs.llmgateway.io/features/image-generation
LLMGateway supports image generation through two APIs:
1. **`/v1/images/generations`** — OpenAI-compatible images endpoint (recommended for simple image generation)
2. **`/v1/images/edits`** — OpenAI-compatible image editing endpoint
3. **`/v1/chat/completions`** — Chat completions with image generation models (for conversational image generation and editing)
For asynchronous video generation, see [Video Generation](https://docs.llmgateway.io/features/video-generation).
## Available Models [#available-models]
You can find all available image generation models on our [models page](https://llmgateway.io/models?filters=1\&imageGeneration=true).
## OpenAI Images API [#openai-images-api]
The `/v1/images/generations` endpoint provides a drop-in replacement for OpenAI's image generation API. It works with any OpenAI-compatible client library.
### Parameters [#parameters]
| Parameter | Type | Default | Description |
| ----------------- | ------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `prompt` | string | required | A text description of the desired image(s) |
| `model` | string | `"auto"` | The model to use. `auto` resolves to `gemini-3-pro-image` |
| `n` | integer | `1` | Number of images to generate (1-10) |
| `size` | string | — | Image dimensions. Supported sizes depend on the model/provider — see [Image Configuration](#image-configuration) |
| `quality` | string | — | Image quality. Supported values depend on the model/provider — see [Image Configuration](#image-configuration) |
| `moderation` | string | `"auto"` | Content filtering strictness for models that support it: `auto` or `low` — see [Moderation](#moderation) |
| `response_format` | string | `"b64_json"` | Only `b64_json` is supported |
| `style` | string | — | Image style: `vivid` or `natural` |
| `service_tier` | string | — | Processing tier for mappings that offer one: `flex`, `priority`, or `default` — see [Service Tiers](https://docs.llmgateway.io/features/service-tiers) |
### curl [#curl]
```bash
curl -X POST "https://api.llmgateway.io/v1/images/generations" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-pro-image",
"prompt": "A cute cat wearing a tiny top hat",
"n": 1,
"size": "1024x1024"
}'
```
### Usage and cost [#usage-and-cost]
Both `/v1/images/generations` and `/v1/images/edits` return a `usage` object with the token counts and the billed cost of the request, in USD:
```json
"usage": {
"input_tokens": 10,
"input_tokens_details": { "image_tokens": 0, "text_tokens": 10 },
"output_tokens": 229,
"output_tokens_details": { "image_tokens": 229, "text_tokens": 0 },
"total_tokens": 239,
"cost": 0.00692,
"cost_details": { "input_cost": 0.00005, "image_output_cost": 0.00687 }
}
```
`cost_details` has the same fields as `usage.cost_details` on [chat completions](#response-format).
### OpenAI SDK [#openai-sdk]
Works with the standard OpenAI client library — just point the base URL to LLMGateway.
```ts
import OpenAI from "openai";
import { writeFileSync } from "fs";
const client = new OpenAI({
baseURL: "https://api.llmgateway.io/v1",
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const response = await client.images.generate({
model: "gemini-3-pro-image",
prompt: "A futuristic city skyline at sunset with flying cars",
n: 1,
size: "1024x1024",
});
response.data.forEach((image, i) => {
if (image.b64_json) {
const buf = Buffer.from(image.b64_json, "base64");
writeFileSync(`image-${i}.png`, buf);
}
});
```
### Vercel AI SDK [#vercel-ai-sdk]
Use the `@llmgateway/ai-sdk-provider` with `generateImage`.
```ts
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
import { generateImage } from "ai";
import { writeFileSync } from "fs";
const llmgateway = createLLMGateway({
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const result = await generateImage({
model: llmgateway.image("gemini-3-pro-image"),
prompt:
"A cozy cabin in a snowy mountain landscape at night with aurora borealis",
size: "1024x1024",
n: 1,
// aspectRatio and quality are model-specific — only some providers honor them.
// aspectRatio works on Gemini image models; OpenAI gpt-image-2 ignores it
// (use a literal WxH `size` instead).
aspectRatio: "16:9",
// quality works on OpenAI gpt-image-2 ("low" | "medium" | "high" | "auto").
// The AI SDK only forwards it through providerOptions.
providerOptions: {
llmgateway: { quality: "high" },
},
});
result.images.forEach((image, i) => {
const buf = Buffer.from(image.base64, "base64");
writeFileSync(`image-${i}.png`, buf);
});
```
## OpenAI Images Edit API [#openai-images-edit-api]
The `/v1/images/edits` endpoint is OpenAI-compatible and supports a focused subset of `images.edit` parameters.
### Parameters [#parameters-1]
| Parameter | Type | Required | Description |
| -------------------- | ------------------------ | -------- | --------------------------------------------------------------------------------- |
| `images` | array of `{ image_url }` | yes | Input images. `image_url` supports HTTPS URLs and base64 data URLs |
| `prompt` | string | yes | A text description of the desired image edit |
| `model` | string | no | Image editing model |
| `background` | enum | no | `transparent`, `opaque`, or `auto` |
| `input_fidelity` | enum | no | `high` or `low` |
| `n` | integer | no | Number of edited images to generate |
| `output_format` | enum | no | `png`, `jpeg`, or `webp` |
| `output_compression` | integer | no | Compression level for `jpeg`/`webp` |
| `quality` | enum | no | `low`, `medium`, `high`, or `auto`; GPT Image 2.5 also supports `xhigh` and `max` |
| `moderation` | enum | no | `auto` or `low` — see [Moderation](#moderation) |
| `size` | string | no | Output size. Examples: `1024x1024`, `1536x1024`, `1K`, `2K`, `4K` |
| `aspect_ratio` | string | no | Aspect ratio override. Examples: `1:1`, `16:9`, `4:3`, `5:4` |
| `service_tier` | enum | no | `flex`, `priority`, or `default` — see [Service Tiers](https://docs.llmgateway.io/features/service-tiers) |
`mask` is not supported yet on `/v1/images/edits`.
### curl (HTTPS image URL) [#curl-https-image-url]
```bash
curl -X POST "https://api.llmgateway.io/v1/images/edits" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": [
{
"image_url": "https://example.com/source-image.png"
}
],
"prompt": "Add a watercolor effect to this image",
"model": "gemini-3-pro-image",
"aspect_ratio": "16:9",
"quality": "high",
"size": "4K"
}'
```
### curl (base64 data URL) [#curl-base64-data-url]
```bash
curl -X POST "https://api.llmgateway.io/v1/images/edits" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"images": [
{
"image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}
],
"prompt": "Turn this into a pixel-art style image"
}'
```
## Chat Completions API [#chat-completions-api]
Image generation also works through the `/v1/chat/completions` endpoint, which is useful for conversational image generation, image editing with vision, and multi-turn interactions.
### Making Requests [#making-requests]
Simply use an image generation model and provide a text prompt describing the image you want to create.
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-pro-image",
"messages": [
{
"role": "user",
"content": "Generate an image of a cute golden retriever puppy playing in a sunny meadow"
}
]
}'
```
### Response Format [#response-format]
Image generation models return responses in the standard chat completions format, with generated images included in the `images` array within the assistant message:
```json
{
"id": "chatcmpl-1756234109285",
"object": "chat.completion",
"created": 1756234109,
"model": "gemini-3-pro-image",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Here's an image of a cute dog for you: ",
"images": [
{
"type": "image_url",
"image_url": {
"url": "data:image/png;base64,"
}
}
]
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 8,
"completion_tokens": 1303,
"total_tokens": 1311
}
}
```
A request stopped by the [gateway content filter](https://docs.llmgateway.io/resources/error-handling#gateway-content-filter) returns `finish_reason: "content_filter"` on the Chat Completions API and an empty `data` array on the Images API.
A provider's own safety rejection is returned the same way, and it is billed the way the provider bills us: a rejection the provider returns as an error carries no usage and costs nothing, apart from a rejection fee the provider publishes (currently only xAI's chat models). A block the provider serves as a normal response with usage, as Gemini image models do, is charged for the input tokens it reports, never for the blocked image.
### Vision support [#vision-support]
You can edit or modify images by combining image generation with [vision models](https://docs.llmgateway.io/features/vision) by including the image in the `messages` array.
### Response Structure [#response-structure]
#### Images Array [#images-array]
The `images` array contains one or more generated images with the following structure:
* `type`: Always `"image_url"` for generated images
* `image_url.url`: A data URL containing the base64-encoded image data (format: `data:image/png;base64,`)
#### Content Field [#content-field]
The `content` field may contain descriptive text about the generated image, depending on the model's behavior.
### AI SDK (Chat Completions) [#ai-sdk-chat-completions]
You can use the AI SDK to generate images with your existing generateText or streamText calls using the LLMGateway provider.
#### Example [#example]
```ts title="/api/chat/route.ts"
import { streamText, type UIMessage, convertToModelMessages } from "ai";
import { createLLMGateway } from "@llmgateway/ai-sdk-provider";
interface ChatRequestBody {
messages: UIMessage[];
}
export async function POST(req: Request) {
const body = await req.json();
const { messages }: ChatRequestBody = body;
const llmgateway = createLLMGateway({
apiKey: "llmgateway_api_key",
baseUrl: "https://api.llmgateway.io/v1",
});
try {
const result = streamText({
model: llmgateway.chat("gemini-3-pro-image"),
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
} catch {
return new Response(
JSON.stringify({ error: "LLM Gateway request failed" }),
{
status: 500,
},
);
}
}
```
Then you can render the image in your frontend using the `Image` component from the [ai-elements](https://ai-sdk.dev/elements/components/image).
Here is a full example of how to use the AI SDK to generate images in your frontend:
```tsx title="/app/page.tsx"
"use client";
import { useState, useRef } from "react";
import { useChat } from "@ai-sdk/react";
import { parseImagePartToDataUrl } from "@/lib/image-utils";
import {
PromptInput,
PromptInputBody,
PromptInputButton,
PromptInputSubmit,
PromptInputTextarea,
PromptInputToolbar,
} from "@/components/ai-elements/prompt-input";
import {
Conversation,
ConversationContent,
} from "@/components/ai-elements/conversation";
import { Image } from "@/components/ai-elements/image";
import { Loader } from "@/components/ai-elements/loader";
import { Message, MessageContent } from "@/components/ai-elements/message";
import { Response } from "@/components/ai-elements/response";
export const ChatUI = () => {
const textareaRef = useRef(null);
const [text, setText] = useState("");
const { messages, status, stop, regenerate, sendMessage } = useChat();
return (
<>
>
);
};
```
```ts title="/lib/image-utils.ts"
/**
* Parses a file object containing image data and returns a properly formatted data URL
* and normalized media type.
*
* Handles:
* - Normalizing mediaType from various property names (mediaType, mime_type)
* - Detecting existing data: URLs
* - Detecting base64-looking content
* - Stripping whitespace from base64 content
* - Building proper data:...;base64,... URLs
*/
export function parseImageFile(file: {
url?: string;
mediaType?: string;
mime_type?: string;
}): { dataUrl: string; mediaType: string } {
const mediaType = file.mediaType || file.mime_type || "image/png";
let url = String(file.url || "");
const isDataUrl = url.startsWith("data:");
const looksLikeBase64 =
!isDataUrl && /^[A-Za-z0-9+/=\s]+$/.test(url.slice(0, 200));
if (looksLikeBase64) {
url = url.replace(/\s+/g, "");
}
const dataUrl = isDataUrl
? url
: looksLikeBase64
? `data:${mediaType};base64,${url}`
: url;
return { dataUrl, mediaType };
}
/**
* Extracts base64-only content from a data URL.
* Returns empty string if the input is not a valid data URL.
*/
export function extractBase64FromDataUrl(dataUrl: string): string {
if (!dataUrl.startsWith("data:")) {
return "";
}
const comma = dataUrl.indexOf(",");
return comma >= 0 ? dataUrl.slice(comma + 1) : "";
}
/**
* Parses an image part (either image_url or file type) and returns
* dataUrl, base64Only, and mediaType ready for rendering.
*
* Handles error cases gracefully by returning empty base64Only string
* when parsing fails, allowing the renderer to skip invalid images.
*/
export function parseImagePartToDataUrl(part: any): {
dataUrl: string;
base64Only: string;
mediaType: string;
} {
try {
// Handle image_url parts
if (part.type === "image_url" && part.image_url?.url) {
const url = part.image_url.url;
const mediaType = "image/png"; // Default for image_url parts
if (url.startsWith("data:")) {
// Extract media type from data URL if present
const match = url.match(/data:([^;]+)/);
const extractedMediaType = match?.[1] || mediaType;
return {
dataUrl: url,
base64Only: extractBase64FromDataUrl(url),
mediaType: extractedMediaType,
};
}
return {
dataUrl: url,
base64Only: "",
mediaType,
};
}
// Handle file parts (AI SDK format)
if (part.type === "file") {
const { dataUrl, mediaType } = parseImageFile(part);
return {
dataUrl,
base64Only: extractBase64FromDataUrl(dataUrl),
mediaType,
};
}
return {
dataUrl: "",
base64Only: "",
mediaType: "image/png",
};
} catch {
return {
dataUrl: "",
base64Only: "",
mediaType: "image/png",
};
}
}
```
## Image Configuration [#image-configuration]
You can customize the generated image using the optional `image_config` parameter (for chat completions) or `size`/`quality`/`style` parameters (for the images API). The supported parameters vary by provider.
### Google Models [#google-models]
Available Google models:
| Model | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------- |
| `gemini-3-pro-image` | Gemini 3 Pro with native image generation. Supports aspect ratios and 1K–4K sizes. |
| `gemini-3.1-flash-image` | Gemini 3.1 Flash with native image generation. Supports 0.5K–4K sizes (default 1K). |
| `gemini-nano-banana-2.1` | Nano Banana 2.1, the Flash-tier successor to Nano Banana 2. Supports 1K–4K sizes (default 1K). |
#### gemini-3-pro-image [#gemini-3-pro-image]
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-pro-image",
"messages": [
{
"role": "user",
"content": "Generate an image of a mountain landscape at sunset"
}
],
"image_config": {
"aspect_ratio": "16:9",
"image_size": "4K"
}
}'
```
| Parameter | Type | Description |
| -------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `aspect_ratio` | string | The aspect ratio of the generated image. Options: `"1:1"`, `"2:3"`, `"3:2"`, `"3:4"`, `"4:3"`, `"4:5"`, `"5:4"`, `"9:16"`, `"16:9"`, `"21:9"` |
| `image_size` | string | The resolution of the generated image. Options: `"1K"` (1024x1024), `"2K"` (2048x2048), `"4K"` (4096x4096) |
#### gemini-3.1-flash-image [#gemini-31-flash-image]
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.1-flash-image",
"messages": [
{
"role": "user",
"content": "Generate an image of a mountain landscape at sunset"
}
],
"image_config": {
"image_size": "1K"
}
}'
```
| Parameter | Type | Description |
| -------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aspect_ratio` | string | The aspect ratio of the generated image. Options: `"1:1"`, `"1:4"`, `"1:8"`, `"2:3"`, `"3:2"`, `"3:4"`, `"4:1"`, `"4:3"`, `"4:5"`, `"5:4"`, `"8:1"`, `"9:16"`, `"16:9"`, `"21:9"` |
| `image_size` | string | The resolution of the generated image. Options: `"0.5K"` (512x512), `"1K"` (1024x1024, default), `"2K"` (2048x2048), `"4K"` (4096x4096) |
`gemini-3.1-flash-image` uniquely supports `"0.5K"` resolution, which is not
available on other Google image models.
### Meta Models [#meta-models]
Muse Image uses Meta's Responses API for generation and editing. It reasons
before rendering and can use reference images across refinement turns.
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "meta/muse-image-1.0",
"messages": [
{
"role": "user",
"content": "Create a product photo on a warm studio background"
}
],
"image_config": {
"image_size": "1024x1536"
}
}'
```
| Parameter | Type | Description |
| ------------ | ------ | -------------------------------------------------------------------------- |
| `image_size` | string | One of `"1024x1024"`, `"1024x1536"`, or `"1536x1024"`. Defaults to square. |
Muse Image does not expose a quality setting. Use `image_size` to choose square,
portrait, or landscape output.
### Alibaba Models [#alibaba-models]
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "alibaba/qwen-image-3.0",
"messages": [
{
"role": "user",
"content": "Generate an image of a mountain landscape at sunset"
}
],
"image_config": {
"image_size": "1024x1536",
"n": 1,
"seed": 42
}
}'
```
| Parameter | Type | Description |
| ------------ | ------- | ------------------------------------------------------------------------------------------------ |
| `image_size` | string | Image dimensions in `WIDTHxHEIGHT` format. Examples: `"1024x1024"`, `"1024x1536"`, `"1536x1024"` |
| `n` | integer | Number of images to generate (1-4) |
| `seed` | integer | Random seed for reproducible generation |
Available Alibaba models (see the [models page](https://llmgateway.io/models?filters=1\&imageGeneration=true) for current pricing):
| Model | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------- |
| `alibaba/qwen-image-2.1-pro` | Generation and editing from up to 10 reference images, flat price per output image |
| `alibaba/qwen-image-2.1-turbo` | Fast, low-cost generation and editing from up to 10 reference images, flat price per output image |
| `alibaba/qwen-image-3.0` | Third-generation image generation and editing |
| `alibaba/qwen-image-3.0-pro` | Highest quality third-generation generation and editing. Priced per output size tier |
Alibaba models use explicit pixel dimensions (e.g., `"1024x1536"`) instead of
aspect ratios. For portrait orientation use `"1024x1536"`, for landscape use
`"1536x1024"`.
### Z.AI Models [#zai-models]
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "zai/cogview-4",
"messages": [
{
"role": "user",
"content": "Generate an image of a futuristic city skyline"
}
],
"image_config": {
"image_size": "1024x1024"
}
}'
```
| Parameter | Type | Description |
| ------------ | ------- | ------------------------------------------------------------------------------------------------ |
| `image_size` | string | Image dimensions in `WIDTHxHEIGHT` format. Examples: `"1024x1024"`, `"2048x1024"`, `"1024x2048"` |
| `n` | integer | Number of images to generate |
Available Z.AI models (see the [models page](https://llmgateway.io/models?filters=1\&imageGeneration=true) for current pricing):
| Model | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------- |
| `zai/cogview-4` | CogView-4 with bilingual support and excellent text rendering |
| `zai/glm-image` | GLM-Image with hybrid auto-regressive architecture, excellent for text-rendering and knowledge-intensive generation |
CogView-4 supports both Chinese and English prompts and excels at generating
images with embedded text.
### OpenAI Models [#openai-models]
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-image-2",
"messages": [
{
"role": "user",
"content": "Generate a photo-real cinematic landscape at golden hour"
}
],
"image_config": {
"image_size": "3072x2160",
"image_quality": "low"
}
}'
```
| Parameter | Type | Description |
| --------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `image_size` | string | Image dimensions in `WIDTHxHEIGHT` format, or `"auto"` to let the model choose. |
| `image_quality` | string | `"low"`, `"medium"`, `"high"`, or `"auto"`; GPT Image 2.5 also supports `"xhigh"` and `"max"`. Defaults to `"auto"` when omitted. |
| `moderation` | string | `"auto"` or `"low"` — see [Moderation](#moderation). Defaults to `"auto"` when omitted. |
OpenAI image models do **not** accept `aspect_ratio`. Always specify
`image_size` as `WIDTHxHEIGHT` (e.g. `"1024x1024"`, `"3072x2160"`). OpenAI
requires both width and height to be divisible by 16, the longest edge to be ≤
3840, and the total pixel count to fit within the model's pixel budget;
requests outside these bounds are rejected with HTTP 400.
Available OpenAI image models:
| Model | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `openai/gpt-image-2` | OpenAI's next-generation image model with improved quality and prompt adherence, supporting text and vision. |
| `openai/gpt-image-2.5-sunburst` | Image generation and precise editing with text and image inputs; adds `xhigh` and `max` quality. |
| `openai/gpt-image-2.5-flare` | Fast everyday image generation and editing with text and image inputs; adds `xhigh` and `max` quality. |
GPT Image 2.5 supports `1024x1024`, `1536x1024`, `1024x1536`, `auto`, and
custom sizes within the limits above. Its aspect ratio must stay between 1:3
and 3:1, with 655,360–8,294,400 total pixels. Resolutions above `2560x1440`
are experimental.
Both variants use the same per-token rates as GPT Image 2. Actual image token
usage varies by model, size, quality, and input; billing uses the provider's
reported usage. See the [models page](https://llmgateway.io/models?filters=1\&imageGeneration=true)
for current pricing.
GPT Image 2 and both GPT Image 2.5 variants are also served through Azure at
the same rates — swap the `openai/` prefix for `azure/` to pin that route, or
send the bare model id and let the gateway pick.
### ByteDance Models [#bytedance-models]
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bytedance/seedream-4-5",
"messages": [
{
"role": "user",
"content": "Generate an image of a futuristic cyberpunk city at night"
}
],
"image_config": {
"image_size": "2048x2048"
}
}'
```
| Parameter | Type | Description |
| ------------ | ------ | ------------------------------------------------------------------------------------------------ |
| `image_size` | string | Image dimensions in `WIDTHxHEIGHT` format. Examples: `"1024x1024"`, `"2048x2048"`, `"4096x4096"` |
Available ByteDance models (see the [models page](https://llmgateway.io/models?filters=1\&imageGeneration=true) for current pricing):
| Model | Description |
| ------------------------------ | --------------------------------------------------------------- |
| `bytedance/seedream-4-0` | High-quality text-to-image generation with 2K default output |
| `bytedance/seedream-4-5` | Enhanced quality and consistency with improved prompt adherence |
| `bytedance/seedream-5-0-pro` | Precise generation and reference-image editing at 1K or 2K |
| `bytedance/seedream-5-0-flash` | Fast, low-cost generation and reference-image editing up to 2K |
Seedream models support up to 2-10 reference images for multi-image fusion and
generation. The default output resolution is 2048×2048 (2K). Seedream 4.0 and
4.5 support up to 4096×4096 (4K); Seedream 5.0 Pro and Flash top out at 2K.
### Tencent Models [#tencent-models]
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "tencent/hy-image-v3.5-preview",
"messages": [
{
"role": "user",
"content": "A neon sign reading \"Open Late\" above a rainy storefront"
}
],
"image_config": {
"image_size": "4096x2304"
}
}'
```
| Parameter | Type | Description |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `image_size` | string | Exact output size in `WIDTHxHEIGHT` format. Each side 256–8192. Omit it to let the model pick the size from the prompt (\~1.5K) |
| `seed` | integer | Seed for reproducible output |
| Model | Description |
| ------------------------------- | -------------------------------------------------------------------------------------- |
| `tencent/hy-image-v3.5-preview` | Generation and editing from up to 20 reference images, up to 4K, strong text in images |
Each request returns one image. Tencent bills a fixed token count per image
(more at 4K); billing uses the token count Tencent reports. See the
[models page](https://llmgateway.io/models?filters=1\&imageGeneration=true) for
current pricing.
## Moderation [#moderation]
GPT Image models expose a `moderation` parameter that controls how strict the
provider's content filtering is:
* `auto` (default) — standard filtering, which limits certain categories of
potentially age-inappropriate content.
* `low` — less restrictive filtering. OpenAI's content policy still applies.
Send it as a top-level parameter on `/v1/images/generations` and
`/v1/images/edits`, or inside `image_config` on `/v1/chat/completions`:
```bash
curl -X POST "https://api.llmgateway.io/v1/images/generations" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-image-2.5-flare",
"prompt": "A cute cat wearing a tiny top hat",
"moderation": "low"
}'
```
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-image-2.5-flare",
"messages": [{ "role": "user", "content": "A cute cat wearing a tiny top hat" }],
"image_config": { "moderation": "low" }
}'
```
Models without a moderation control ignore the parameter.
## Usage Notes [#usage-notes]
Image generation models typically have higher token costs compared to
text-only models due to the computational requirements of image synthesis.
Generated images are returned as base64-encoded data URLs, which can be large.
Consider the payload size when integrating image generation into your
applications.
# Master Keys
URL: https://docs.llmgateway.io/features/master-keys
Master keys are org-scoped bearer tokens that let you create projects, gateway API keys, IAM rules (both per key and per organization member), and your organization's custom providers and custom models programmatically — without going through the dashboard. They are intended for server-to-server provisioning (e.g. multi-tenant onboarding from your own backend).
They also expose [usage and cost reporting](#usage-and-cost-reporting), so you can pull per-member and per-model spend into your own dashboards, data warehouse, or chargeback process.
Master keys are available on the **Enterprise** plan only. Contact us at
[contact@llmgateway.io](mailto:contact@llmgateway.io) to enable them for your organization.
## Security [#security]
* Master keys are stored as **HMAC-SHA256 hashes** in the database (using the `GATEWAY_API_KEY_HASH_SECRET` secret). The plain token is shown to you **only once** at creation time.
* Each master key is scoped to a single organization and cannot access resources in other organizations.
* Deleting or deactivating a master key revokes all programmatic access immediately.
* All creates/deletes/status changes are recorded in your organization audit log.
## Limits [#limits]
* Maximum **10 active master keys per organization**.
* Programmatic project and API-key creation enforces the same per-org and per-project limits as the dashboard flow.
## Managing master keys [#managing-master-keys]
In the dashboard, go to **Organization → Master Keys**. From there you can:
* Create a new master key (the plain token is shown once — copy it immediately).
* View the masked token, status, creator, and last-used timestamp for each existing key.
* Activate / deactivate or delete keys.
## Authentication [#authentication]
All programmatic endpoints live under `/v1/master/*` and require a master key in the `Authorization` header:
```
Authorization: Bearer llmgmk_...
```
A request with a missing, invalid, inactive, or non-enterprise master key receives a 401 / 403 response.
## Endpoints [#endpoints]
### List projects [#list-projects]
`GET /v1/master/projects`
Returns all non-deleted projects in the master key's organization.
```bash
curl https://internal.llmgateway.io/v1/master/projects \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{
"projects": [
{
"id": "proj_...",
"name": "Customer ACME",
"organizationId": "org_...",
"cachingEnabled": false,
"cacheDurationSeconds": 60,
"mode": "hybrid",
"status": "active",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
### Create a project [#create-a-project]
`POST /v1/master/projects`
```bash
curl -X POST https://internal.llmgateway.io/v1/master/projects \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer ACME",
"cachingEnabled": false,
"mode": "hybrid"
}'
```
Body parameters:
| Field | Type | Description |
| ---------------------- | ------------------------------------------------ | -------------------------- |
| `name` | string | Project name (1–255 chars) |
| `cachingEnabled` | boolean (optional) | Default `false` |
| `cacheDurationSeconds` | number (optional) | 10–31536000, default 60 |
| `mode` | `"api-keys" \| "credits" \| "hybrid"` (optional) | Default `"hybrid"` |
Response (201): the created project.
### Update a project [#update-a-project]
`PATCH /v1/master/projects/{id}`
Updates a project owned by the master key's organization. All body fields are optional; provide only the ones you want to change.
```bash
curl -X PATCH https://internal.llmgateway.io/v1/master/projects/proj_... \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer ACME (renamed)",
"cachingEnabled": true,
"status": "inactive"
}'
```
Body parameters (all optional, at least one required):
| Field | Type | Description |
| -------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
| `name` | string | 1–255 chars |
| `cachingEnabled` | boolean | |
| `cacheDurationSeconds` | number | 10–31536000 |
| `providerCacheControlMode` | `"auto" \| "passthrough" \| "off"` | [Provider cache writes](https://docs.llmgateway.io/features/caching/provider-cache-control) |
| `mode` | `"api-keys" \| "credits" \| "hybrid"` | |
| `status` | `"active" \| "inactive"` | Toggle the project without deleting |
Response (200): the updated project. It carries both `providerCacheControlMode` and the older `providerCacheControlEnabled` boolean, which reports `false` only for `"off"`.
### Delete a project [#delete-a-project]
`DELETE /v1/master/projects/{id}`
Soft-deletes a project (sets `status` to `"deleted"`). Cascades to its API keys. Mirroring the dashboard's owner-only project deletion, this endpoint returns a `403` unless the user who created the master key is currently an **owner** of the organization.
```bash
curl -X DELETE https://internal.llmgateway.io/v1/master/projects/proj_... \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{ "message": "Project deleted successfully" }
```
### List gateway API keys [#list-gateway-api-keys]
`GET /v1/master/keys`
Returns the developer-created gateway API keys in the master key's organization, each with its creator, project, configured limits, the usage consumed so far, and — when a windowed limit is set — the time the current period resets. `projectName` provides the human-readable project name alongside `projectId`. `createdBy` contains the creator's internal user ID, while `createdByEmail` contains their email address (`null` when unavailable). Pass an optional `projectId` query parameter to scope the list to a single project.
```bash
curl "https://internal.llmgateway.io/v1/master/keys?projectId=proj_..." \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{
"apiKeys": [
{
"id": "ak_...",
"description": "Customer ACME — production key",
"status": "active",
"projectId": "proj_...",
"projectName": "Customer ACME",
"createdBy": "usr_...",
"createdByEmail": "member@example.com",
"maskedToken": "llmgtwy_...abcd",
"usageLimit": "100.00",
"usage": "42.13",
"periodUsageLimit": "10.00",
"periodUsageDurationValue": 1,
"periodUsageDurationUnit": "day",
"currentPeriodUsage": "3.50",
"currentPeriodStartedAt": "2025-01-15T00:00:00.000Z",
"currentPeriodResetAt": "2025-01-16T00:00:00.000Z",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
Limit and usage fields:
| Field | Description |
| -------------------------- | ---------------------------------------------------------------------------------- |
| `usageLimit` | Lifetime spend cap (`null` when uncapped) |
| `usage` | Total spend accrued against `usageLimit` over the key's lifetime |
| `periodUsageLimit` | Recurring per-window spend cap (`null` when no windowed limit is configured) |
| `periodUsageDurationValue` | Length of the window, paired with `periodUsageDurationUnit` |
| `periodUsageDurationUnit` | `"hour" \| "day" \| "week" \| "month"` |
| `currentPeriodUsage` | Spend accrued in the current window (`"0"` when unconfigured or the window lapsed) |
| `currentPeriodStartedAt` | When the current window began (`null` when unconfigured or lapsed) |
| `currentPeriodResetAt` | When the windowed limit resets (`null` when unconfigured or lapsed) |
The plain token is never returned by this endpoint — only a masked form for identification.
### Get a gateway API key [#get-a-gateway-api-key]
`GET /v1/master/keys/{id}`
Returns a single gateway API key in the master key's organization, with the same limit, usage, and reset-time fields as the list endpoint.
```bash
curl https://internal.llmgateway.io/v1/master/keys/ak_... \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{
"apiKey": {
"id": "ak_...",
"description": "Customer ACME — production key",
"status": "active",
"projectId": "proj_...",
"projectName": "Customer ACME",
"createdBy": "usr_...",
"createdByEmail": "member@example.com",
"maskedToken": "llmgtwy_...abcd",
"usageLimit": "100.00",
"usage": "42.13",
"periodUsageLimit": "10.00",
"periodUsageDurationValue": 1,
"periodUsageDurationUnit": "day",
"currentPeriodUsage": "3.50",
"currentPeriodStartedAt": "2025-01-15T00:00:00.000Z",
"currentPeriodResetAt": "2025-01-16T00:00:00.000Z",
"createdAt": "...",
"updatedAt": "..."
}
}
```
### Create a gateway API key [#create-a-gateway-api-key]
`POST /v1/master/keys`
```bash
curl -X POST https://internal.llmgateway.io/v1/master/keys \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "proj_...",
"description": "Customer ACME — production key"
}'
```
Body parameters:
| Field | Type | Description |
| -------------------------- | ------------------------------------------------- | -------------------------------------------- |
| `projectId` | string | Must belong to the master key's organization |
| `description` | string | API key description (1–255 chars) |
| `usageLimit` | string (optional) | Lifetime usage limit |
| `periodUsageLimit` | string (optional) | Recurring period usage limit |
| `periodUsageDurationValue` | number (optional) | Required if `periodUsageLimit` is set |
| `periodUsageDurationUnit` | `"hour" \| "day" \| "week" \| "month"` (optional) | Required if `periodUsageLimit` is set |
The created gateway API key's plain token is returned in the response **only
once**. Persist it immediately on your side.
Response (201):
```json
{
"apiKey": {
"id": "ak_...",
"token": "llmgtwy_...",
"description": "Customer ACME — production key",
"status": "active",
"projectId": "proj_...",
"createdBy": "usr_...",
"createdAt": "...",
"updatedAt": "..."
}
}
```
### Update a gateway API key [#update-a-gateway-api-key]
`PATCH /v1/master/keys/{id}`
Updates an API key in a project owned by the master key's organization. All body fields are optional; provide only the ones you want to change.
```bash
curl -X PATCH https://internal.llmgateway.io/v1/master/keys/ak_... \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "inactive",
"usageLimit": "100.00"
}'
```
Body parameters (all optional, at least one required):
| Field | Type | Description |
| -------------------------- | -------------------------------------- | -------------------------------------- |
| `description` | string | 1–255 chars |
| `status` | `"active" \| "inactive"` | |
| `usageLimit` | string \| null | Lifetime usage limit (null to clear) |
| `periodUsageLimit` | string \| null | Recurring period limit (null to clear) |
| `periodUsageDurationValue` | number \| null | Required if `periodUsageLimit` is set |
| `periodUsageDurationUnit` | `"hour" \| "day" \| "week" \| "month"` | Required if `periodUsageLimit` is set |
Response (200): the updated API key, including its configured limits, consumed `usage` / `currentPeriodUsage`, and the `currentPeriodResetAt` window-reset time (same fields as the [list endpoint](#list-gateway-api-keys)). The plain token is **not** included — it is only returned at creation.
Two dashboard-only key features have no master API equivalent yet: [expiration
(TTL)](https://docs.llmgateway.io/features/api-keys#expiration-ttl) and [rolling a key's
secret](https://docs.llmgateway.io/features/api-keys#rotating-rolling-api-keys). To replace a key
programmatically, create a new one and delete the old one once your clients
have switched over.
### Delete a gateway API key [#delete-a-gateway-api-key]
`DELETE /v1/master/keys/{id}`
Soft-deletes the API key (sets `status` to `"deleted"`). Any in-flight requests using the key will be rejected immediately on next auth check.
```bash
curl -X DELETE https://internal.llmgateway.io/v1/master/keys/ak_... \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{ "message": "API key deleted successfully" }
```
The managed Playground API key cannot be updated or deleted via the master
API.
## Usage and cost reporting [#usage-and-cost-reporting]
`GET /v1/master/usage`
Returns usage and cost for the master key's organization as a flat list of rows, grouped by any combination of member, model, provider, project, and API key, and bucketed hourly, daily, or not at all. This is the endpoint to point an internal reporting tool, data warehouse, or chargeback job at — it is the only usage surface that authenticates with a token rather than a dashboard session.
Like every `/v1/master/*` endpoint, this requires an **Enterprise** plan. A
master key on any other plan receives a `403`.
```bash
# Cost per member for a month
curl -G https://internal.llmgateway.io/v1/master/usage \
-H "Authorization: Bearer $MASTER_KEY" \
-d from=2026-07-01 -d to=2026-07-31 \
-d granularity=total -d groupBy=user
```
Query parameters:
| Param | Type | Default | Description |
| ------------- | ----------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------- |
| `from`, `to` | `YYYY-MM-DD` | last 7 days | Inclusive window, interpreted as wall-clock days in `timezone`. Max 366 days |
| `timezone` | IANA timezone | `UTC` | Timezone the day/hour buckets are labelled in |
| `granularity` | `hour` \| `day` \| `total` | `day` | Time bucket. `total` collapses the window into one row per group. `hour` caps at 31 days |
| `groupBy` | comma-separated: `user`, `model`, `provider`, `project`, `apiKey` | `user,model` | Dimensions to break down by. Pass an empty value for organization-wide totals |
| `projectId` | string | — | Restrict to one project. `404` if it is not in this organization |
| `userId` | string | — | Restrict to one member |
| `apiKeyId` | string | — | Restrict to one gateway API key |
| `limit` | 1–10000 | `1000` | Rows per page |
| `offset` | ≥ 0 | `0` | Row offset for paging |
| `format` | `json` \| `csv` | `json` | `csv` returns a `text/csv` attachment with a fixed header |
Response (200):
```json
{
"from": "2026-07-01",
"to": "2026-07-31",
"granularity": "day",
"groupBy": ["user", "model"],
"rows": [
{
"date": "2026-07-01",
"userId": "usr_...",
"userName": "Ada Lovelace",
"userEmail": "ada@example.com",
"projectId": null,
"projectName": null,
"apiKeyId": null,
"apiKeyName": null,
"model": "gpt-5.6",
"provider": "openai",
"requestCount": 128,
"errorCount": 2,
"inputTokens": 481203,
"outputTokens": 92844,
"totalTokens": 574047,
"cachedTokens": 120000,
"reasoningTokens": 18320,
"cost": 3.4127,
"inputCost": 2.1,
"outputCost": 1.3127,
"creditsRequestCount": 128,
"apiKeysRequestCount": 0,
"creditsCost": 3.4127,
"apiKeysCost": 0
}
],
"pagination": { "limit": 1000, "offset": 0, "hasMore": false }
}
```
Every row carries the full column set. Dimension fields you did not group by are `null`, and `date` is `null` when `granularity=total`, so the row shape stays stable for a schema-driven consumer. `creditsCost` / `apiKeysCost` split the blended `cost` into credit-billed and BYOK traffic.
### Per-member, per-model breakdown [#per-member-per-model-breakdown]
Combine the two dimensions to get the cross-tab most reporting tools want — one row per member, model, and day:
```bash
curl -G https://internal.llmgateway.io/v1/master/usage \
-H "Authorization: Bearer $MASTER_KEY" \
-d from=2026-07-01 -d to=2026-07-31 \
-d granularity=day -d groupBy=user,model \
-d timezone=Europe/Berlin
```
### CSV export [#csv-export]
```bash
curl -G https://internal.llmgateway.io/v1/master/usage \
-H "Authorization: Bearer $MASTER_KEY" \
-d from=2026-07-01 -d to=2026-07-31 \
-d granularity=day -d groupBy=user,model -d format=csv \
-o usage-july.csv
```
The CSV header is the same fixed column set regardless of the dimensions requested, so a scheduled export never changes shape.
**Usage is attributed to the member who created the API key** (there is no
per-caller identity on an inference request). A key shared by several people
reports entirely under its creator. For accurate per-person reporting, issue
one gateway API key per member — the per-member key limit under **Organization
→ Members** is the lever that enforces it.
Two more things worth knowing:
* These figures come from hourly rollups, not the request log, so they are **not** affected by your [data retention](https://docs.llmgateway.io/features/data-retention) setting — per-member and per-model history stays available even with retention turned off.
* Traffic from [embeddable payments](https://docs.llmgateway.io/features/embeddable-payments) end-user keys rolls up to the member who provisioned the platform key.
## IAM rules [#iam-rules]
Each gateway API key can have one or more IAM rules that restrict which models, providers, or pricing tiers it is allowed to use. Rules are evaluated at request time by the gateway. A key with no active rules has no IAM restrictions.
Rule types:
| `ruleType` | Description |
| ----------------- | ----------------------------------------------------------- |
| `allow_models` | Only the listed models are permitted |
| `deny_models` | The listed models are blocked |
| `allow_providers` | Only the listed providers are permitted |
| `deny_providers` | The listed providers are blocked |
| `allow_pricing` | Only models matching the pricing constraint are permitted |
| `deny_pricing` | Models matching the pricing constraint are blocked |
| `allow_ip_cidrs` | Only requests from the listed IPv4/IPv6 CIDRs are permitted |
| `deny_ip_cidrs` | Requests from the listed IPv4/IPv6 CIDRs are blocked |
The `ruleValue` JSON object holds the rule's parameters. The fields it accepts depend on the `ruleType`:
| Field | Type | Used by |
| ---------------- | ------------------ | ----------------------------------- |
| `models` | string\[] | `allow_models`, `deny_models` |
| `providers` | string\[] | `allow_providers`, `deny_providers` |
| `pricingType` | `"free" \| "paid"` | `allow_pricing`, `deny_pricing` |
| `maxInputPrice` | number | `allow_pricing`, `deny_pricing` |
| `maxOutputPrice` | number | `allow_pricing`, `deny_pricing` |
| `ipCidrs` | string\[] | `allow_ip_cidrs`, `deny_ip_cidrs` |
### IP CIDR rules [#ip-cidr-rules]
IP CIDR rules restrict gateway requests by source IP. Both IPv4 (e.g. `192.0.2.0/24`) and IPv6 (e.g. `2001:db8::/32`) ranges are supported, and you can mix both in a single rule. To restrict to a single address, use a `/32` (IPv4) or `/128` (IPv6) prefix.
The gateway reads the client IP from a single header, named by the `CLIENT_IP_HEADER` environment variable. On the hosted service the load balancer overwrites that header on every request, so a value sent by the client cannot spoof the address. Self-hosted deployments default to `X-Forwarded-For`; point `CLIENT_IP_HEADER` at a header your reverse proxy overwrites rather than appends to.
IPv4-mapped IPv6 addresses (`::ffff:1.2.3.4`) are normalized to IPv4 so a single `1.2.3.0/24` rule still matches when the upstream connection happens to be IPv6.
When an `allow_ip_cidrs` rule is configured and the gateway cannot determine the client IP, the request is denied. Invalid CIDR syntax is rejected at rule-creation time with a `400` error.
All endpoints scope by the master key's organization: a `404` is returned if the API key (or rule) is not part of the authenticated master key's organization.
### List IAM rules [#list-iam-rules]
`GET /v1/master/keys/{id}/iam`
```bash
curl https://internal.llmgateway.io/v1/master/keys/ak_.../iam \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{
"rules": [
{
"id": "iam_...",
"apiKeyId": "ak_...",
"ruleType": "allow_models",
"ruleValue": {
"models": ["openai/gpt-4o", "anthropic/claude-3-5-sonnet"]
},
"status": "active",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
### Create an IAM rule [#create-an-iam-rule]
`POST /v1/master/keys/{id}/iam`
```bash
curl -X POST https://internal.llmgateway.io/v1/master/keys/ak_.../iam \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"ruleType": "allow_models",
"ruleValue": {
"models": ["openai/gpt-4o", "anthropic/claude-3-5-sonnet"]
}
}'
```
Body parameters:
| Field | Type | Description |
| ----------- | ------------------------ | ------------------------------------------------------- |
| `ruleType` | rule type enum (above) | Required |
| `ruleValue` | object (see table above) | Must include the fields appropriate for the chosen type |
| `status` | `"active" \| "inactive"` | Optional, defaults to `"active"` |
Restricting by source IP:
```bash
curl -X POST https://internal.llmgateway.io/v1/master/keys/ak_.../iam \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"ruleType": "allow_ip_cidrs",
"ruleValue": {
"ipCidrs": ["192.0.2.0/24", "2001:db8::/32"]
}
}'
```
Response (201): the created IAM rule.
### Update an IAM rule [#update-an-iam-rule]
`PATCH /v1/master/keys/{id}/iam/{ruleId}`
All body fields are optional; provide only the ones you want to change.
```bash
curl -X PATCH https://internal.llmgateway.io/v1/master/keys/ak_.../iam/iam_... \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "inactive"
}'
```
Body parameters (all optional, at least one required):
| Field | Type | Description |
| ----------- | ------------------------ | --------------------------------------- |
| `ruleType` | rule type enum (above) | Change the rule type |
| `ruleValue` | object (see table above) | Replace the rule value |
| `status` | `"active" \| "inactive"` | Activate or deactivate without deleting |
Response (200): the updated IAM rule.
### Delete an IAM rule [#delete-an-iam-rule]
`DELETE /v1/master/keys/{id}/iam/{ruleId}`
Permanently removes an IAM rule from the API key.
```bash
curl -X DELETE https://internal.llmgateway.io/v1/master/keys/ak_.../iam/iam_... \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{ "message": "IAM rule deleted successfully" }
```
## Member IAM rules [#member-iam-rules]
The same rule types can be applied to an **organization member** instead of a single API key. Member-level rules are an organization-wide ceiling: a request must pass both the member's rules and the key's rules, so key rules can only narrow access further, never expand it. They apply to all regular API keys created by that member. See [Member-Level IAM Rules](https://docs.llmgateway.io/features/api-keys#member-level-iam-rules) for the full semantics.
The `{member}` path parameter accepts either the **membership id** or the member's **email address** (matched case-insensitively). Email references must resolve to a user who is a member of the master key's organization — an email belonging to a user outside the organization, or an unknown reference, returns a `404`.
The `ruleType`, `ruleValue`, and `status` fields are identical to the per-key IAM endpoints above.
### List member IAM rules [#list-member-iam-rules]
`GET /v1/master/members/{member}/iam`
```bash
curl https://internal.llmgateway.io/v1/master/members/jane@example.com/iam \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{
"rules": [
{
"id": "iam_...",
"userOrganizationId": "uo_...",
"ruleType": "allow_providers",
"ruleValue": {
"providers": ["openai"]
},
"status": "active",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
### Create a member IAM rule [#create-a-member-iam-rule]
`POST /v1/master/members/{member}/iam`
```bash
curl -X POST https://internal.llmgateway.io/v1/master/members/jane@example.com/iam \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"ruleType": "allow_providers",
"ruleValue": {
"providers": ["openai"]
}
}'
```
Response (201): the created member IAM rule.
### Update a member IAM rule [#update-a-member-iam-rule]
`PATCH /v1/master/members/{member}/iam/{ruleId}`
All body fields are optional; provide only the ones you want to change.
```bash
curl -X PATCH https://internal.llmgateway.io/v1/master/members/uo_.../iam/iam_... \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "inactive"
}'
```
Response (200): the updated member IAM rule.
### Delete a member IAM rule [#delete-a-member-iam-rule]
`DELETE /v1/master/members/{member}/iam/{ruleId}`
```bash
curl -X DELETE https://internal.llmgateway.io/v1/master/members/uo_.../iam/iam_... \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{ "message": "Member IAM rule deleted successfully" }
```
## Custom providers [#custom-providers]
Custom providers are your own OpenAI-compatible endpoints, registered as BYOK provider keys with `provider: "custom"`. The gateway routes to them with the model string `/` (e.g. `acme/acme-large`). See [Custom Providers](https://docs.llmgateway.io/features/custom-providers) for the dashboard flow and routing semantics.
Only custom providers are exposed through the master API — catalog providers (OpenAI, Anthropic, …) require an interactive upstream credential check and must be added from the dashboard.
All endpoints scope by the master key's organization: a `404` is returned if the provider key is not a non-deleted custom provider in that organization.
The provider token is stored but never returned. Responses only include a
`maskedToken` for identification.
### List custom providers [#list-custom-providers]
`GET /v1/master/custom-providers`
```bash
curl https://internal.llmgateway.io/v1/master/custom-providers \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{
"customProviders": [
{
"id": "pk_...",
"provider": "custom",
"name": "acme",
"baseUrl": "https://llm.acme.example.com",
"maskedToken": "sk-a...cret",
"status": "active",
"customModelsOnly": true,
"complianceAttestation": null,
"organizationId": "org_...",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
### Get a custom provider [#get-a-custom-provider]
`GET /v1/master/custom-providers/{id}`
Returns a single custom provider in the master key's organization.
### Create a custom provider [#create-a-custom-provider]
`POST /v1/master/custom-providers`
```bash
curl -X POST https://internal.llmgateway.io/v1/master/custom-providers \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "acme",
"baseUrl": "https://llm.acme.example.com",
"token": "sk-acme-...",
"customModelsOnly": true
}'
```
Body parameters:
| Field | Type | Description |
| ----------------------- | ------------------ | -------------------------------------------------------------------------------------------- |
| `name` | string | Lowercase letters and single hyphens only. Unique per organization; used in the model string |
| `baseUrl` | string | OpenAI-compatible base URL. Internal/reserved addresses are rejected |
| `token` | string | Upstream API key. Stored server-side, never returned |
| `customModelsOnly` | boolean (optional) | Restrict the provider to models defined in your custom catalog. Default `false` |
| `complianceAttestation` | object (optional) | Self-attested compliance posture. `attestedAt` / `attestedByUserId` are stamped server-side |
Response (201): the created custom provider.
### Update a custom provider [#update-a-custom-provider]
`PATCH /v1/master/custom-providers/{id}`
All body fields are optional; provide only the ones you want to change.
```bash
curl -X PATCH https://internal.llmgateway.io/v1/master/custom-providers/pk_... \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"baseUrl": "https://llm2.acme.example.com",
"token": "sk-acme-rotated-..."
}'
```
Body parameters (all optional, at least one required):
| Field | Type | Description |
| ----------------------- | ------------------------ | ---------------------------------------------- |
| `baseUrl` | string | Repoint the provider at a new endpoint |
| `token` | string | Rotate the upstream API key |
| `status` | `"active" \| "inactive"` | Disable the provider without deleting it |
| `customModelsOnly` | boolean | Toggle the custom-catalog restriction |
| `complianceAttestation` | object \| null | Replace the attestation, or `null` to clear it |
The provider `name` cannot be changed through the master API — it is the routing segment your model strings reference. Owners and admins can rename a custom provider from the dashboard, which changes the `/` prefix clients must use.
Response (200): the updated custom provider.
### Delete a custom provider [#delete-a-custom-provider]
`DELETE /v1/master/custom-providers/{id}`
Soft-deletes the provider (sets `status` to `"deleted"`). Its custom models stop resolving immediately.
Response (200):
```json
{ "message": "Custom provider deleted successfully" }
```
## Custom models [#custom-models]
Custom models are your organization's catalogue entries for a custom provider: the context window, output limit, per-token pricing, and capability flags the gateway uses to route, validate, and bill requests to that model. When a provider has `customModelsOnly: true`, only models in this catalogue can be called through it.
Query these endpoints to read the full catalogue you have registered — including `contextSize`, `maxOutput`, and every price field — for surfacing in your own dashboards or cost tooling.
Per-token prices are strings in USD per token. Use `e-6` notation so the
coefficient reads directly as USD per million tokens — `"3.0e-6"` is $3.00/M.
### List custom models [#list-custom-models]
`GET /v1/master/custom-models`
Returns the non-deleted custom models in the master key's organization. Pass an optional `providerKeyId` query parameter to scope the list to a single custom provider.
```bash
curl "https://internal.llmgateway.io/v1/master/custom-models?providerKeyId=pk_..." \
-H "Authorization: Bearer $MASTER_KEY"
```
Response (200):
```json
{
"customModels": [
{
"id": "cm_...",
"providerKeyId": "pk_...",
"organizationId": "org_...",
"modelName": "acme-large",
"displayName": "Acme Large",
"contextSize": 200000,
"maxOutput": 32000,
"inputPrice": "3.0e-6",
"outputPrice": "15.0e-6",
"cachedInputPrice": "0.3e-6",
"streaming": "true",
"vision": true,
"tools": true,
"reasoning": null,
"jsonOutput": true,
"supportedParameters": ["temperature", "top_p"],
"status": "active",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
### Get a custom model [#get-a-custom-model]
`GET /v1/master/custom-models/{id}`
Returns a single custom model in the master key's organization.
### Create a custom model [#create-a-custom-model]
`POST /v1/master/custom-models`
```bash
curl -X POST https://internal.llmgateway.io/v1/master/custom-models \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"providerKeyId": "pk_...",
"modelName": "acme-large",
"displayName": "Acme Large",
"contextSize": 200000,
"maxOutput": 32000,
"inputPrice": "3.0e-6",
"outputPrice": "15.0e-6",
"tools": true
}'
```
Body parameters:
| Field | Type | Description |
| ------------------------ | ----------------------------------- | ---------------------------------------------------------- |
| `providerKeyId` | string | Must be a custom provider in the master key's organization |
| `modelName` | string | Model id sent upstream. Unique per provider |
| `displayName` | string (optional) | Human-readable label |
| `contextSize` | number (optional) | Context window in tokens |
| `maxOutput` | number (optional) | Max output tokens |
| `inputPrice` | string (optional) | USD per input token |
| `outputPrice` | string (optional) | USD per output token |
| `cachedInputPrice` | string (optional) | USD per cached input token |
| `cacheReadInputPrice` | string (optional) | USD per cache-read token |
| `cacheWriteInputPrice` | string (optional) | USD per cache-write token (5m TTL) |
| `cacheWriteInputPrice1h` | string (optional) | USD per cache-write token (1h TTL) |
| `requestPrice` | string (optional) | Flat USD charged per request |
| `webSearchPrice` | string (optional) | USD per web search |
| `imageInputPrice` | string (optional) | USD per image input token |
| `audioInputPrice` | string (optional) | USD per audio input token |
| `streaming` | `"true" \| "false" \| "only"` | Streaming support (`"only"` = streaming-only model) |
| `vision` | boolean (optional) | Accepts image input |
| `tools` | boolean (optional) | Supports tool calling |
| `reasoning` | boolean (optional) | Emits reasoning output |
| `jsonOutput` | boolean (optional) | Supports JSON / structured output |
| `audio` | boolean (optional) | Accepts audio input |
| `supportedParameters` | string\[] (optional) | Request parameters the upstream accepts |
| `status` | `"active" \| "inactive"` (optional) | Defaults to `"active"` |
Response (201): the created custom model.
### Update a custom model [#update-a-custom-model]
`PATCH /v1/master/custom-models/{id}`
Accepts the same fields as create (except `providerKeyId`), all optional — provide only the ones you want to change. Renaming to a `modelName` already used by another model on the same provider returns a `400`.
```bash
curl -X PATCH https://internal.llmgateway.io/v1/master/custom-models/cm_... \
-H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "contextSize": 400000, "outputPrice": "12.0e-6" }'
```
Response (200): the updated custom model.
### Delete a custom model [#delete-a-custom-model]
`DELETE /v1/master/custom-models/{id}`
Soft-deletes the model (sets `status` to `"deleted"`).
Response (200):
```json
{ "message": "Custom model deleted successfully" }
```
# Metadata
URL: https://docs.llmgateway.io/features/metadata
LLM Gateway supports sending additional metadata with your requests using custom headers. This allows you to include information like user sessions, application versions, tenant IDs, or other contextual data that can be useful for analytics and monitoring.
Later, you can filter by specific values to return, such as for a specific user or session. Additionally, in the future, you will be able to segment your analytics and monitoring based on this metadata. For example, you could show cost and latency breakdowns per user, application, country, feature, or any other dimension you want to track.
## Custom Headers [#custom-headers]
You can include custom headers with the `X-LLMGateway-` prefix to send metadata alongside your LLM requests:
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "X-LLMGateway-Country: US" \
-H "X-LLMGateway-User-ID: 9403f741-a524-4b18-b1b2-dbb71cdff2a4" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "Hello, how are you?"
}
]
}'
```
## Best Practices [#best-practices]
### Header Naming [#header-naming]
* Use the `X-LLMGateway-` prefix for all custom metadata
* Use descriptive, consistent naming conventions
* Avoid special characters; use hyphens to separate words
### Data Privacy [#data-privacy]
* Be mindful of sensitive data in headers
* Consider hashing or anonymizing user identifiers
* Follow your organization's data privacy policies
### Performance [#performance]
* Keep header values reasonably short
* Avoid sending unnecessary metadata that won't be used for analytics
* Consider the impact on request size, especially for high-volume applications
## Example: Multi-tenant Application [#example-multi-tenant-application]
For a multi-tenant application, you might use metadata headers like this:
```bash
curl -X POST https://api.llmgateway.io/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "X-LLMGateway-Tenant-ID: acme-corp" \
-H "X-LLMGateway-User-ID: user-12345" \
-H "X-LLMGateway-App-Version: 2.1.4" \
-H "X-LLMGateway-Feature: chat-assistant" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "Summarize this document..."
}
]
}'
```
This allows you to track usage and costs per tenant, user, application version, and feature, providing detailed insights into how your LLM integration is being used across your platform.
# Org Models Directory
URL: https://docs.llmgateway.io/features/models-directory
The **Models** page in the dashboard (`Organization → Models`) lists every model your organization can route to in a single directory: the full LLM Gateway catalogue plus the models defined for your own [custom providers](https://docs.llmgateway.io/features/custom-providers). It answers two questions for your whole team:
* **What can we call?** — every catalogue and custom model, searchable and filterable by capability, provider, price, and context size, exactly like the public [models page](https://llmgateway.io/models).
* **What are we allowed to call?** — when a [provider compliance policy](https://docs.llmgateway.io/features/compliance) is active, each provider mapping is marked eligible or blocked using the same fail-closed evaluation the gateway applies at request time.
## Who can see it [#who-can-see-it]
Every active organization member can browse the directory read-only — including project-scoped **developer** members, who see it as their only organization page. This is the recommended way for developers to discover available providers and models without being granted additional permissions. Management actions (custom-model catalog, attestations) remain restricted to organization owners and admins.
## Custom models in the directory [#custom-models-in-the-directory]
Custom models appear alongside catalogue models, addressed as `{customProvider}/{modelName}` — the exact model string to send to the gateway:
```bash
curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "internal-vllm/llama-4-maverick",
"messages": [{ "role": "user", "content": "Hello!" }]
}'
```
Their pricing, context and output limits, and capability flags come from the [custom model catalog](https://docs.llmgateway.io/features/custom-providers#custom-model-catalog). A **Source** filter switches between catalogue models, custom models, or both.
## Compliance eligibility [#compliance-eligibility]
With an enabled compliance policy (Enterprise), the directory evaluates every provider mapping against the policy — certifications, data-policy requirements, country restrictions, and provider/model allow and deny lists:
* Blocked mappings render greyed out with a ban icon; its tooltip lists the exact failing requirements (for example "No SOC 2 Type 2 report" or "Not on the allowed-providers list").
* Custom providers are evaluated against their [compliance attestation](https://docs.llmgateway.io/features/compliance#custom-providers); without one on file they are blocked while a policy is active.
* An **Eligible only** filter narrows the directory to models the gateway will actually route to.
* A blocked model can be watched, so the organization hears when a compliant provider starts serving it — see [compliance alerts](https://docs.llmgateway.io/features/compliance#compliance-alerts).
The directory mirrors gateway enforcement — the same predicates decide both
the markers on this page and the 403 responses at request time. If a model is
marked blocked here, requests for it will be rejected while the policy is
active.
## Discover models with an API key [#discover-models-with-an-api-key]
Call `GET /v1/models` with your API key to list models available under your
organization's compliance policy, inherited team and member IAM rules, and the
key's own IAM rules. Blocked provider mappings are omitted; models with no
remaining mappings are omitted too. Pricing and capabilities reflect the
remaining mappings. Accessible custom models use `{customProvider}/{modelName}` IDs.
```bash
curl "https://api.llmgateway.io/v1/models" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY"
```
The endpoint also accepts the `x-api-key` header. Without either authentication
header, it returns the public catalogue. Invalid, inactive, or expired credentials
return `401`.
Set `?include_restricted=true` to return the full public catalogue even with an
authenticated API key. This skips organization, team, member, key, project, and
plan filtering for discovery. Private custom models are omitted. Other query
filters still apply, and inference requests still enforce all access restrictions.
The default is `false`, which keeps authenticated results filtered.
Use `?mapped=true` for provider-prefixed entries. Existing query filters, including
`no_training`, apply alongside access restrictions. Discovery reflects model
permissions and project configuration; it does not check remaining credits,
usage budgets, or current provider health.
## Related [#related]
* [Knowledge base → Models](https://docs.llmgateway.io/learn/models) — a walkthrough of the page itself.
* [Custom Providers](https://docs.llmgateway.io/features/custom-providers) — bring your own OpenAI-compatible endpoints and define their model catalog.
* [Compliance](https://docs.llmgateway.io/features/compliance) — configure the provider compliance policy.
# Moderations
URL: https://docs.llmgateway.io/features/moderations
LLMGateway supports the OpenAI-compatible `/v1/moderations` endpoint for text
and multimodal safety classification.
Use it when you want to:
* Screen user prompts before they reach a model
* Review generated output before displaying it
* Apply the same moderation API shape you already use with OpenAI clients
For the full request and response schema, see the
[API reference](https://docs.llmgateway.io/v1_moderations).
## Endpoint [#endpoint]
`POST https://api.llmgateway.io/v1/moderations`
Authenticate with your LLMGateway API key:
```bash
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY"
```
## Supported Inputs [#supported-inputs]
The `input` field accepts:
* A single string
* An array of strings
* An array of multimodal content items with `text` and `image_url`
The default model is `omni-moderation-latest`.
## Pricing [#pricing]
Starting **Friday, August 7, 2026**, moderation requests are billed at a
flat **$0.00001 per request**, independent of the input size, the number of
inputs, or the moderation model you pick. Only successful requests are billed —
failed and retried attempts cost nothing. Before that date, moderation requests
are free.
Because the endpoint becomes paid, it also starts requiring a credit balance
from that date: moderation requests from an organization with no credits are
rejected with `402`. Top up before August 7 to avoid an interruption.
When your project runs in `api-keys` mode and the request is served with your
own OpenAI key, no credits are deducted and no balance is required.
## curl [#curl]
### Single text input [#single-text-input]
```bash
curl -X POST "https://api.llmgateway.io/v1/moderations" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "I want to harm someone."
}'
```
### Multiple text inputs [#multiple-text-inputs]
```bash
curl -X POST "https://api.llmgateway.io/v1/moderations" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "omni-moderation-latest",
"input": [
"This is a harmless sentence.",
"I want to attack somebody."
]
}'
```
### Multimodal input [#multimodal-input]
```bash
curl -X POST "https://api.llmgateway.io/v1/moderations" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": [
{
"type": "text",
"text": "Check this image for violent content."
},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/image.png"
}
}
]
}'
```
## OpenAI SDK [#openai-sdk]
```ts
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.llmgateway.io/v1",
apiKey: process.env.LLM_GATEWAY_API_KEY,
});
const response = await client.moderations.create({
model: "omni-moderation-latest",
input: "I want to harm someone.",
});
console.log(response.results[0]?.flagged);
```
## Response Shape [#response-shape]
The response follows the standard OpenAI moderation format:
```json
{
"id": "modr-123",
"model": "omni-moderation-latest",
"results": [
{
"flagged": true,
"categories": {
"violence": true,
"self_harm": false
},
"category_scores": {
"violence": 0.98,
"self_harm": 0.01
}
}
]
}
```
## When To Use This Instead Of Chat Content Filtering [#when-to-use-this-instead-of-chat-content-filtering]
Use `/v1/moderations` when you want an explicit moderation decision in your own
application flow.
If you want moderation to happen automatically as part of model requests, use
LLMGateway content filtering on `/v1/chat/completions` instead.
# Notifications
URL: https://docs.llmgateway.io/features/notifications
Open the **Notifications** bell in the dashboard header, then select **Notification settings**. Choose **In-app**, **Email**, both, or neither for each alert type. Most alerts start disabled; the compliance alerts start enabled for everyone in the organization's alert audience, and organization limit alerts start enabled for owners and admins. Email delivery always requires a verified email address. Your preferences apply across projects you can access.
| Alert | When it appears |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| API key budgets | A regular API key reaches your selected percentage of its lifetime or recurring budget. The default threshold is 80%. |
| Model retirements | A model mapping you used in the last 30 days is scheduled for deprecation or deactivation within 30 days. The alert links to the model catalogue so you can choose a newer model. |
| Provider issues | A provider you used in the last 30 days has elevated upstream errors in its recent traffic. |
| Compliance: models available | A model your organization watches becomes routable under its [compliance policy](https://docs.llmgateway.io/features/compliance#compliance-alerts). |
| Compliance: downgrades | A provider, or a watched model, stops meeting your organization's compliance policy. |
| Organization limits | A seat or API key limit blocks directory sync (SCIM), an SSO sign-in, an invite, or a new API key. Sent to owners and admins. |
Organization limit alerts are sent at most once per day for each kind of limit; rejected directory sync pushes are also listed in the [audit log](https://docs.llmgateway.io/features/audit-logs). Budget alerts repeat for each new recurring period, or when you change the limit or alert threshold. Unlimited keys do not trigger budget warnings. Each scheduled model retirement is announced once; ongoing provider issues produce at most one alert per day per project, or per accessible key for developers.
Retirement dates apply per provider mapping. A mapping can be retired when its provider redirects requests to a newer model. Check the [model catalogue](https://llmgateway.io/models) and update your requested model before the mapping's deactivation date.
Provider alerts require at least 20 non-cached, non-client-error requests and an upstream error rate of at least 20% in the latest hourly statistics window. Stale statistics do not trigger alerts. Checks run about once a minute; these warnings are not a guarantee that every outage or budget crossing will be caught before a request fails.
[Compliance alerts](https://docs.llmgateway.io/features/compliance#compliance-alerts) follow organization role instead of project access: you receive them while your role is inside the audience the organization selected, and leaving that audience hides them from the inbox and stops pending email. They are also the only alerts that can be posted to a shared Slack channel, once per organization rather than per recipient.
Every other notification follows your existing project permissions. Developers receive alerts only for their own keys in assigned projects. Removed project access also removes those alerts from the inbox and prevents pending email delivery.
The inbox shows your 50 most recent in-app alerts. Open an alert to follow its action, or select **Mark all as read** to clear the unread indicator. Turn off a channel in notification settings to stop future delivery through it.
# OCR
URL: https://docs.llmgateway.io/features/ocr
LLMGateway exposes a dedicated `/v1/ocr` endpoint for optical character
recognition. It extracts text, tables, and layout from PDFs and images and
returns them as clean markdown, one entry per page.
Use it when you want to:
* Turn scanned PDFs or photos into machine-readable markdown
* Pull structured text out of receipts, invoices, forms, or screenshots
* Feed document contents into a downstream model or RAG pipeline
For the full request and response schema, see the
[API reference](https://docs.llmgateway.io/v1_ocr).
## Endpoint [#endpoint]
`POST https://api.llmgateway.io/v1/ocr`
Authenticate with your LLMGateway API key:
```bash
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY"
```
The current model is `mistral-ocr-latest`, billed at **$4 per 1,000 pages**
processed.
## Document Input [#document-input]
The `document` field accepts either a document URL (PDF) or an image:
* `{ "type": "document_url", "document_url": "https://…/file.pdf" }`
* `{ "type": "image_url", "image_url": "https://…/image.png" }`
Both `document_url` and `image_url` accept a public URL or a base64 data URL
(`data:application/pdf;base64,…` / `data:image/png;base64,…`). The `image_url`
field may also be passed as an object: `{ "url": "…" }`.
### Scoping pages [#scoping-pages]
By default the entire document is processed and every page is billed. Use the
optional `pages` field to restrict (and cap the cost of) a request:
* A list of zero-based indices: `"pages": [0, 1, 2]`
* A range string: `"pages": "0-4"`
## curl [#curl]
### Document URL [#document-url]
```bash
curl -X POST "https://api.llmgateway.io/v1/ocr" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-ocr-latest",
"document": {
"type": "document_url",
"document_url": "https://arxiv.org/pdf/2201.04234"
}
}'
```
### Only specific pages [#only-specific-pages]
```bash
curl -X POST "https://api.llmgateway.io/v1/ocr" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-ocr-latest",
"document": {
"type": "document_url",
"document_url": "https://arxiv.org/pdf/2201.04234"
},
"pages": "0-4"
}'
```
### Image input [#image-input]
```bash
curl -X POST "https://api.llmgateway.io/v1/ocr" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-ocr-latest",
"document": {
"type": "image_url",
"image_url": "https://example.com/receipt.png"
}
}'
```
### Inline (base64) document [#inline-base64-document]
```bash
BASE64_PDF=$(base64 -i invoice.pdf)
curl -X POST "https://api.llmgateway.io/v1/ocr" \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"mistral-ocr-latest\",
\"document\": {
\"type\": \"document_url\",
\"document_url\": \"data:application/pdf;base64,${BASE64_PDF}\"
}
}"
```
## Response Shape [#response-shape]
```json
{
"pages": [
{
"index": 0,
"markdown": "# Document title\n\nExtracted body text…",
"images": [],
"dimensions": { "dpi": 200, "height": 2200, "width": 1700 }
}
],
"model": "mistral-ocr-latest",
"document_annotation": null,
"usage_info": {
"pages_processed": 1,
"doc_size_bytes": 125344
}
}
```
Each entry in `pages` carries the markdown for one page. `usage_info.pages_processed`
reflects exactly how many pages were billed for the request.
## Billing [#billing]
OCR is billed per page processed, not per token. A request that processes 12
pages bills `12 × $0.004 = $0.048`. Scoping a request with `pages` reduces both
the work and the cost.
## OCR Models Use This Endpoint, Not Chat [#ocr-models-use-this-endpoint-not-chat]
OCR models are not chat models and cannot be called through
`/v1/chat/completions` — doing so returns a `400` pointing you here. Send OCR
requests to `/v1/ocr`.
# Organization skills
URL: https://docs.llmgateway.io/features/organization-skills
Organization skills are reusable instructions and supporting files that enterprise teams share through the CLI. Owners and admins publish skills in the dashboard; developers can discover and download them using a project API key from the same organization.
## Publish a skill [#publish-a-skill]
1. Open **Organization → Skills** in the dashboard.
2. Choose **Import SKILL.md** for a single skill, **Import folder** for a skill with references, scripts, or assets, or **New custom skill** to write instructions.
3. Review the content and save. New skills are enabled immediately.
A skill folder must contain `SKILL.md` at its root. Its YAML header needs a unique lowercase, hyphenated `name` of up to 64 characters and a `description` of up to 1,024 characters. Write the instructions below the header:
```md
---
name: code-review
description: Review code changes against the team's standards.
---
# Code review
- Check correctness and error handling.
- Explain the impact of each finding.
- Suggest tests for important edge cases.
```
Bundles support up to 100 additional files and 1 MB of total content. `SKILL.md` supports up to 200,000 characters within that limit. Supporting paths must be relative to the skill folder; duplicate paths and parent-directory traversal are rejected. Existing YAML fields, such as `license` or `allowed-tools`, are preserved.
## Manage access [#manage-access]
All active organization members can view skills. Owners and admins can edit, disable, or delete them. Keep a published skill's name unchanged; create a new skill to use a different name.
Disabling a skill removes it from CLI discovery and prevents new downloads. Deleting it also removes its stored content. These actions cannot remove copies already downloaded to a developer's machine. Publishing and management actions appear in the organization's audit log without recording the skill content.
## CLI API contract [#cli-api-contract]
These endpoints belong to the [platform API](https://internal.llmgateway.io/docs). Authenticate with `Authorization: Bearer `, using a regular project API key issued to an active member with access to that project.
| Method | Path | Response |
| ------ | ------------------- | ------------------------------------------------ |
| GET | `/v1/skills` | `{ skills: [...] }` with enabled skill summaries |
| GET | `/v1/skills/{name}` | `{ skill: { ...summary, content, files } }` |
A summary contains `id`, `name`, `description`, `enabled`, `createdAt`, and `updatedAt`. Timestamps use ISO 8601. `content` is the complete `SKILL.md`, including its YAML header. Each supporting file contains a relative `path`, `content`, and optional `encoding` (`utf-8` by default, or `base64` for binary files).
Clients should discover skills at the start of a session, download selected skills by name, and write each bundle into its own directory. Revalidate authorization before reusing organization content and remove cached availability when access is denied. Treat supporting scripts as code to review before running.
The server derives the organization from the API key. Expired or revoked keys return `401`; removed project access or missing enterprise access returns `403`; missing or disabled skills return `404`. Responses use `Cache-Control: private, no-store`.
Dashboard management uses session authentication at `/orgs/{organizationId}/skills`: `GET` lists summaries and `POST` creates a bundle. At `/orgs/{organizationId}/skills/{id}`, `GET` reads a bundle, `PUT` replaces `{ content, files }`, `PATCH` sets `{ enabled }`, and `DELETE` removes it. Duplicate names return `409`.
# Project access
URL: https://docs.llmgateway.io/features/project-access
Use **Project admin** to let someone manage specific projects without giving them organization administration. Owners and admins assign the role and its project grants from [Team → Members](https://docs.llmgateway.io/learn/team).
| Role | Scope | Permissions |
| ------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Owner | All organization projects | Organization administration, billing settings, membership, and project management |
| Admin | All organization projects | Organization and project management; cannot change billing settings or modify owners |
| Project admin | Assigned projects | Project settings, routing, dynamic routes, guardrails, Payments SDK settings and platform keys, all project API keys, and project-wide usage |
| Developer | Assigned projects | Own API keys and own usage; cannot change project settings |
Project admin and Developer assignments require Enterprise access and at least one project grant. Existing feature and preview requirements still apply. Project admins cannot create or archive projects, change organization settings or provider keys, manage membership, or access organization billing controls. Archiving projects and deleting organizations require an Owner.
## Assign project access [#assign-project-access]
In **Team → Members**, choose **Add Member** or an existing member's **Manage access** action. Select **Project admin**, choose the allowed projects, and save. Invitations carry the selected grants through acceptance. Removing a grant removes access to that project's settings, keys, and usage; the gateway also checks the creator's project access when a key is used, subject to normal cache propagation.
For an authenticated management API session, use `project_admin` with `projectIds` when adding a member:
```json
{
"email": "member@example.com",
"role": "project_admin",
"projectIds": ["your-project-id"]
}
```
Send this body to `POST /team/{organizationId}/members`. Use `PATCH /team/{organizationId}/members/{memberId}` with `role` and the complete `projectIds` list to replace an existing member's access.
## Budgets, teams, and SSO [#budgets-teams-and-sso]
Personal member budgets and IAM rules still apply to the member's keys. Organization teams and default developer budgets apply only to Developers; promoting a Developer clears their team assignment.
[SSO group mappings](https://docs.llmgateway.io/features/sso) can assign `project_admin`. Role priority is Owner, Admin, Project admin, then Developer. Project-scoped roles still require explicit project grants: a role mapping does not grant every project.
Project guardrails can display the organization policy they inherit, while changes to that policy remain restricted to organization administrators.
# Prompt Management
URL: https://docs.llmgateway.io/features/prompt-management
Prompt management keeps your prompts in LLM Gateway instead of your code. Each prompt has immutable versions, and named labels such as `production` and `staging` point at them. Change a prompt, move a label, and every request that follows that label picks it up without a redeploy.
Manage prompts on the [**Prompts**](https://docs.llmgateway.io/learn/prompts) page of a project, or through the API.
## Templates and variables [#templates-and-variables]
A version holds a list of messages, an optional default model, and optional parameters (`temperature`, `top_p`, `max_tokens`, `frequency_penalty`, `presence_penalty`, `reasoning_effort` from `none` to `max`). Write `{{name}}` anywhere in a message to declare a variable. Names start with a letter or underscore and may contain letters, digits, `_`, `.`, and `-`.
## Labels and versions [#labels-and-versions]
Saving a change always creates a new version; existing versions never change. Labels are named pointers at a version:
* `production` is what requests use when they pick nothing else. Creating a prompt points it at version 1.
* Any other label (`staging`, `canary`, a customer name) lets you run one version somewhere while `production` serves another.
* `latest` always means the newest version. It is implicit and cannot be assigned.
Deploying is moving `production`. Roll back by pointing it at an earlier version. `production` can be moved but not removed, so a request that names no label always resolves. Label names start with a letter and may contain letters, digits, `_`, `.`, and `-`.
## Calling a prompt [#calling-a-prompt]
Reference a prompt through `model`, or send a `prompt` object:
```bash
curl https://api.llmgateway.io/v1/chat/completions \
-H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "@prompt/support-reply",
"messages": [{ "role": "user", "content": "How do I rotate my key?" }]
}'
```
`model: "@prompt/"` works with any OpenAI-compatible client, including tools that only let you set a base URL and a model. Add `@