Connect Clients
Clients reach the LLM Gateway with two values, both on Control > LLM Gateway > Connect:
| Value | What it is |
|---|---|
| Gateway URL | The gateway's base URL, for example https://acme.withwillow.ai/llm |
| API key command | npx -y @mcp-s/mcp@latest auth token --base-url <your MCP URL>. It prints the user's Willow credential, which the client sends as its API key |
Every person gets their own key, so usage, budgets, and logs are attributed to the person who signed in. Nobody needs a provider API key.
How the key works
The first time the command runs on a machine, it opens the browser and signs the user in to Willow. After that, it prints the credential saved in ~/.willow/token.json, the same file the Willow stdio MCP server uses, so a user who already has the stdio server set up is already signed in. The command needs Node.js on the machine.
Clients that can run a command to get their key, such as Claude Code's apiKeyHelper, should run this command rather than store its output, so the user never handles the credential.
If a client gets 401 Invalid or expired Willow token, the user signs in again:
npx -y @mcp-s/mcp@latest auth login --refresh --base-url <your MCP URL>
auth status shows whether the user is signed in, and auth logout removes the saved credential.
Which URL each API uses
The gateway serves the APIs natively and does not translate between them:
| Client API | Base URL to set | Serves |
|---|---|---|
| Anthropic Messages | The Gateway URL as is | Models on an Anthropic provider |
| OpenAI Chat Completions or Responses | The Gateway URL followed by /v1 | Models on an OpenAI or OpenAI-compatible provider |
Calling a model through the other API is refused with Model … is served by …; call it through ….
Claude Code
Add the gateway to Claude Code's settings.json, either the user's own or a managed one:
{
"apiKeyHelper": "npx -y @mcp-s/mcp@latest auth token --base-url https://acme.withwillow.ai/mcp",
"env": {
"ANTHROPIC_BASE_URL": "https://acme.withwillow.ai/llm"
}
}
The Connect tab shows this snippet with your organization's values filled in. Remove ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN from the environment: Claude Code prefers them over apiKeyHelper, so they would bypass the user's Willow key.
Roll it out with the Scan Agent
Instead of editing each machine, use Roll out to Claude Code with the Scan Agent at the top of the Connect tab:
- Choose an existing Claude Code policy, or New policy: LLM Gateway.
- Select Add and deploy, or Create and deploy for a new policy. Willow adds the gateway URL and the key command to the policy and removes
ANTHROPIC_API_KEYandANTHROPIC_AUTH_TOKENfrom it. If the policy already has both, the button reads Deploy. - Deployments opens with the policy selected. Choose whether it goes to the whole organization, to groups, or to single users.
Each user signs in once, the first time Claude Code needs a key.
The card only appears when Config Deployment is enabled for your organization and you have the Monitoring & Shadow AI > Manage permission. See also Claude Code Policy.
Codex
In ~/.codex/config.toml:
model = "gpt-5"
model_provider = "willow"
[model_providers.willow]
name = "Willow LLM Gateway"
base_url = "https://acme.withwillow.ai/llm/v1"
env_key = "WILLOW_TOKEN"
wire_api = "responses"
Then set the key before starting Codex:
export WILLOW_TOKEN="$(npx -y @mcp-s/mcp@latest auth token --base-url https://acme.withwillow.ai/mcp)"
Anthropic and OpenAI SDKs
Pass the gateway as the base URL and the Willow credential as the API key. In Python:
import subprocess
from anthropic import Anthropic
token = subprocess.check_output(
["npx", "-y", "@mcp-s/mcp@latest", "auth", "token", "--base-url", "https://acme.withwillow.ai/mcp"],
text=True,
).strip()
client = Anthropic(base_url="https://acme.withwillow.ai/llm", api_key=token)
reply = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
For the OpenAI SDK, use OpenAI(base_url="https://acme.withwillow.ai/llm/v1", api_key=token) the same way.
The model is an alias or a provider model id from your Models tab.
Any HTTP client
The gateway accepts the key as Authorization: Bearer … or x-api-key. List the models the caller may use:
curl https://acme.withwillow.ai/llm/v1/models \
-H "Authorization: Bearer $(npx -y @mcp-s/mcp@latest auth token --base-url https://acme.withwillow.ai/mcp)"
The list contains only the models the caller's groups allow. Send an anthropic-version header to get it in Anthropic's shape, with Anthropic models only.
Endpoints
All paths are under the Gateway URL:
| Endpoint | API |
|---|---|
POST /v1/messages | Anthropic Messages, streamed or not |
POST /v1/messages/count_tokens | Anthropic token counting. Forwarded with your key; no budgets or guards |
POST /v1/chat/completions | OpenAI Chat Completions, streamed or not |
POST /v1/responses | OpenAI Responses, used by Codex, streamed or not |
GET /v1/models | The models the caller may use |
Name your client in the logs
The gateway labels each call with the client that made it: claude-code, codex, or cursor from the user agent, and api for anything else. The label appears in logs and is what a guard's AI agent filter matches. Your own apps can set their label with an x-willow-client header of up to 40 letters, digits, ., _, or -.