Connections / Model tools
Connect model tools to Muniment
Use https://api.muniment.ai as the model endpoint to keep model access, budgets, and usage receipts governed by your organization. Ask an organization administrator for your Muniment virtual key and an entitled model name. The key is an identity credential: keep it out of source control, command history, support messages, and application logs.
Wire protocols
The endpoint accepts three wire protocols through the same virtual-key policy:
- Anthropic Messages at
/v1/messages - OpenAI Responses at
/v1/responses - OpenAI-compatible chat completions at
/v1/chat/completions
Changing protocol does not change access. A model outside your entitlements is refused before a provider call, and the same key budget applies across tools. Successful calls produce usage receipts attributed to the organization and user represented by the virtual key.
Claude Code
Claude Code uses the Anthropic Messages protocol. Set these variables in the shell that launches claude:
export ANTHROPIC_BASE_URL="https://api.muniment.ai"
export ANTHROPIC_AUTH_TOKEN="<muniment-virtual-key>"
unset ANTHROPIC_API_KEY
claude --model "<entitled-model>"Claude Code supplies the required anthropic-version header. Feature-specific anthropic-beta headers are passed through unchanged.
For fleet-managed installations, deploy the following managed-settings.json through MDM or the platform's system-level managed-settings location:
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.muniment.ai"
}
}Inject ANTHROPIC_AUTH_TOKEN separately for each identity; do not put a shared virtual key in managed settings.
Codex CLI
Export the key, then add a provider to ~/.codex/config.toml:
export MUNIMENT_API_KEY="<muniment-virtual-key>"model_provider = "muniment"
model = "<entitled-model>"
[model_providers.muniment]
name = "Muniment"
base_url = "https://api.muniment.ai/v1"
env_key = "MUNIMENT_API_KEY"
wire_api = "responses"Codex sends OpenAI Responses requests. Keep wire_api = "responses"; chat completions is not a Codex CLI wire mode.
aider
aider uses OpenAI-compatible chat completions for this connection:
export OPENAI_API_BASE="https://api.muniment.ai/v1"
export OPENAI_API_KEY="<muniment-virtual-key>"
aider --model "openai/<entitled-model>"If the model is not in aider's built-in metadata, its model warning does not grant or deny access; Muniment's server-side entitlement remains authoritative.
OpenCode
Export the key:
export MUNIMENT_API_KEY="<muniment-virtual-key>"Add a custom OpenAI-compatible provider to opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"muniment": {
"npm": "@ai-sdk/openai-compatible",
"name": "Muniment",
"options": {
"baseURL": "https://api.muniment.ai/v1",
"apiKey": "{env:MUNIMENT_API_KEY}"
},
"models": {
"<entitled-model>": {
"name": "<entitled-model>"
}
}
}
}
}Select muniment/<entitled-model> in /models. This provider uses chat completions. For an OpenCode model that specifically requires Responses, use "npm": "@ai-sdk/openai" with the same base URL and key.
OpenAI Agents SDK for Python
Install openai-agents, export an identity's virtual key and entitled model, then run this complete example:
export MUNIMENT_API_KEY="<muniment-virtual-key>"
export MUNIMENT_MODEL="<entitled-model>"import os
from agents import Agent, OpenAIChatCompletionsModel, Runner, set_tracing_disabled
from openai import AsyncOpenAI
key = os.environ["MUNIMENT_API_KEY"]
model_name = os.environ["MUNIMENT_MODEL"]
# Muniment governs model calls, but does not accept OpenAI trace exports.
set_tracing_disabled(True)
client = AsyncOpenAI(
api_key=key,
base_url="https://api.muniment.ai/v1",
)
agent = Agent(
name="Muniment assistant",
instructions="Answer concisely.",
model=OpenAIChatCompletionsModel(
model=model_name,
openai_client=client,
),
)
result = Runner.run_sync(agent, "Name one benefit of least-privilege access.")
print(result.final_output)OpenAI Agents SDK for TypeScript
Create a project and install the SDKs and TypeScript runner:
npm init -y
npm install @openai/agents openai tsxExport the same two variables shown in the Python example and save this complete example as muniment-agent.ts:
import {
Agent,
run,
setDefaultOpenAIClient,
setOpenAIAPI,
setTracingDisabled,
} from "@openai/agents";
import OpenAI from "openai";
async function main() {
const apiKey = process.env.MUNIMENT_API_KEY;
const model = process.env.MUNIMENT_MODEL;
if (!apiKey || !model) {
throw new Error("Set MUNIMENT_API_KEY and MUNIMENT_MODEL");
}
setDefaultOpenAIClient(new OpenAI({
apiKey,
baseURL: "https://api.muniment.ai/v1",
}));
setOpenAIAPI("chat_completions");
setTracingDisabled(true);
const agent = new Agent({
name: "Muniment assistant",
instructions: "Answer concisely.",
model,
});
const result = await run(
agent,
"Name one benefit of least-privilege access.",
);
console.log(result.finalOutput);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});Run it with the installed TypeScript runner:
npx tsx muniment-agent.tsThese examples deliberately select chat completions, the OpenAI-compatible agent-framework path verified by the gateway integration suite. Features that require OpenAI-hosted services, including OpenAI trace export, are not routed through Muniment.
CrewAI 1.15.2
Install the exact version exercised by the gateway integration suite:
python -m pip install "crewai==1.15.2"
export MUNIMENT_AGENT_BEARER="<short-lived-muniment-agent-bearer>"
export MUNIMENT_MODEL="<entitled-model>"Use CrewAI's public LLM configuration with the Muniment URL and agent bearer. The explicit provider="openai" selects CrewAI's OpenAI-compatible chat client without rewriting or restricting Muniment's public model name.
import os
from crewai import LLM
llm = LLM(
model=os.environ["MUNIMENT_MODEL"],
provider="openai",
api_key=os.environ["MUNIMENT_AGENT_BEARER"],
base_url="https://api.muniment.ai/v1",
max_tokens=64,
)
print(llm.call("Name one benefit of least-privilege access."))Do not substitute an OpenAI or other provider key. Version 1.15.2 is verified to honor the configured Muniment base URL without OpenAI-host key validation.
PydanticAI 2.9.1
Install the exact verified version and inject the same kind of short-lived agent bearer:
python -m pip install "pydantic-ai==2.9.1"
export MUNIMENT_AGENT_BEARER="<short-lived-muniment-agent-bearer>"
export MUNIMENT_MODEL="<entitled-model>"import os
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider
model = OpenAIChatModel(
os.environ["MUNIMENT_MODEL"],
provider=OpenAIProvider(
base_url="https://api.muniment.ai/v1",
api_key=os.environ["MUNIMENT_AGENT_BEARER"],
),
)
agent = Agent(model)
print(agent.run_sync("Name one benefit of least-privilege access.").output)These two configurations are connection proofs: the suite verifies one non-streaming chat-completions result per framework, tenant-scoped receipt provenance, provider-boundary redaction, and rejection of a model entitled only to another organization. Framework tools, capabilities, streaming, and framework-managed tracing are outside this verification scope.
Other OpenAI-compatible tools
Configure the tool's OpenAI base URL as https://api.muniment.ai/v1, its API key as your Muniment virtual key, and its model as an entitled model name. The tool must use either /v1/chat/completions or /v1/responses; changing the URL to a provider endpoint bypasses Muniment and does not produce a Muniment usage receipt.
Troubleshooting
401: the virtual key is missing, invalid, expired, revoked, or blocked.403: the requested model is not entitled for this identity. Changing the client or wire protocol will not change the decision.429: the virtual-key budget or a gateway rate limit has been reached.502: the gateway or configured model provider is temporarily unavailable.
Never replace the virtual key with a provider API key. Provider credentials remain server-side.
Verified client scope
The gateway integration suite exercises Anthropic Messages, OpenAI Responses, and OpenAI-compatible chat completions with real virtual-key model scope, budget enforcement, provider routing, and usage receipts. It also verifies forwarding of Claude Code's anthropic-version and anthropic-beta headers. The Python and TypeScript agent examples above use the agent SDKs' documented custom OpenAI client and chat-completions selection against that verified protocol. The TypeScript example's documented tsx execution path was also verified with the current packages. CrewAI 1.15.2 and PydanticAI 2.9.1 are additionally exercised as real pinned clients with short-lived agent bearers, including a cross-tenant model denial before provider dispatch.
This page makes support claims only for the clients and configurations shown above. Claude Desktop, Claude's VS Code extension, Codex desktop, and subscription-auth interactions have not been verified against the shipped gateway and are intentionally not described as supported configurations.
Operator appendix: Configure the beta homelab provider vault
The beta homelab may store organization provider credentials with a deployment-secret-store supplied local key-encryption key. Configure the same values on the API and provider resolver services:
PROVIDER_VAULT_KMS_PROVIDER=local
PROVIDER_VAULT_LOCAL_KEK_ID=local:homelab-primary
PROVIDER_VAULT_LOCAL_KEK_ACTIVE_VERSION=1
PROVIDER_VAULT_LOCAL_KEK_KEYRING='{"1":"<padded base64 encoding of exactly 32 random bytes>"}'Generate the key outside the repository and inject the keyring through the deployment secret store. Keep PROVIDER_VAULT_LOCAL_KEK_ID stable across rotations. Keep key-store backups separate from database backups. Never put the keyring in a repository file, image, command-line argument, log, or diagnostic response.
For a local rotation, add the new version to the keyring, select it as the active version, restart (the startup check verifies every retained key), then create and run the durable rewrap operation:
npm run provider-vault:rewrap -- create local:homelab-primary 1 operator@example.com
npm run provider-vault:rewrap -- resume <operation-id>The first command prints the operation ID. resume is safe to repeat after an interruption. Do not remove the old keyring entry until the operation reports completion and a subsequent restart passes readiness.
For local-to-AWS migration, retain all three local settings while changing PROVIDER_VAULT_KMS_PROVIDER to aws-kms and setting PROVIDER_VAULT_KMS_KEY_ID. Restart to verify both readers, create the operation using the old local ID/version as above, and resume it to completion. Only then remove the local settings and restart. The operation rewrites wrapped DEKs and their KEK references; provider ciphertext is unchanged.