Overview for Developers
Brutor is a gateway. You don’t rewrite your application to use it — you point each client at the gateway (one host, with its own path for each kind of AI resource: LLM, MCP, A2A, skills) and your existing OpenAI SDK, Anthropic SDK, MCP client, or plain HTTP code keeps working. The moment your traffic flows through the gateway, it picks up, with zero agent-side code:
- Guardrails — prompt-injection and jailbreak detection, PII redaction or blocking, secrets detection, toxic-content filtering, banned words — on inputs and outputs across every surface. See Guardrails.
- Budgets and limits — requests-per-minute, tokens-per-day, dollars-per-minute, concurrency caps, per group and per model. See Limits.
- Failover and routing — routing groups retry, cool down failing models, and fall back to secondaries without your code noticing.
- Audit trail — every request, tool call, block, and approval decision is logged with cost, latency, and the identity that made it. See Logs.
- Cost tracking — per-token cost attribution by user, group, and model in Mission Control.
- Human-in-the-loop approvals — sensitive actions pause (the caller gets
202 Accepted) until a person approves them. See Approvals. - Agent identity — bind your key to an agent identity and it is default-deny: only the models, tools, skills and agents you grant are reachable. See Agent identity & policies.
And with a few assurance headers on each call:
- Assurance — calls grouped into runs are checked against your AI System’s signed contract and learned baseline; drift raises a finding. See The run ledger.
# Beforeclient = OpenAI(api_key="sk-...")
# After — same code, now governed (MCP, A2A and skills have their own paths)client = OpenAI( api_key="sk_brutor_api_...", base_url="http://localhost:8100/v1/proxy/llm",)The runtime surfaces
Section titled “The runtime surfaces”All developer-facing runtime traffic goes to the Core Proxy (Rust, port 8100). The Control Plane (5050) is admin-only configuration — you only touch it from setup scripts or the Admin UI.
┌──────────────────────────────────────────┐ Your app / agent ────▶│ Core Proxy :8100 │ │ │ OpenAI SDK ──────────▶│ /v1/proxy/llm/* LLM (OpenAI-compat) Anthropic SDK ───────▶│ /v1/messages LLM (Anthropic native) MCP client ──────────▶│ /v1/proxy/mcp/{id} MCP tool servers MCP client ──────────▶│ /v1/proxy/vmcp/{id} Virtual MCP (aggregated) A2A peer ────────────▶│ /a2a/{tenant}/{card} A2A v1.0, inbound Your agent ──────────▶│ /v1/proxy/a2a/outbound A2A to remote agents Agent runtime ───────▶│ /v1/proxy/mcp/system- Agent Skills (MCP, │ agent-skill-server-* progressive disclosure) OAuth client ────────▶│ /v1/proxy/oauth/* OAuth proxy for MCP tools Custom portal ───────▶│ /v1/portal/* Portal API (end-user auth, │ models, approvals, notifications) └──────────────────────────────────────────┘ │ guardrails · limits · routing approvals · audit · cost tracking| Surface | Base path | What it’s for |
|---|---|---|
| LLM proxy | /v1/proxy/llm |
OpenAI-compatible chat, completions, responses, embeddings, images, audio, video |
| Anthropic native | /v1/messages |
Anthropic SDK and Claude Code, unmodified |
| MCP | /v1/proxy/mcp/{server_id} |
JSON-RPC 2.0 to any registered MCP server (streamable HTTP) |
| Virtual MCP | /v1/proxy/vmcp/{virtual_server_id} |
One endpoint aggregating tools from many MCP servers |
| A2A v1.0 (inbound) | /a2a/{tenant_id}/{card_name}/message:send |
Peers call agents the gateway hosts: messaging, tasks, signed agent cards |
| A2A outbound | /v1/proxy/a2a/outbound |
Your agent calls a remote A2A agent by its card id; guardrails, audit and the signed delegation chain apply |
| Agent Skills | /v1/proxy/mcp/system-agent-skill-server-{tenant} |
Progressive-disclosure skills over MCP — load instructions, run scripts in the governed sandbox |
| OAuth proxy | /v1/proxy/oauth/{mcp_server_id}/{idp_server_id} |
Per-user OAuth for SaaS-backed MCP tools |
| Batch & files | /v1/proxy/batch/jobs, /v1/proxy/files |
Async batch jobs and multimodal file upload over the same governed LLM surface. See the LLM proxy reference |
| Portal API | /v1/portal/* |
End-user auth, model/server discovery, approvals, notifications — the API the shipped User Portal runs on |
Authentication: API key, workload token or portal JWT
Section titled “Authentication: API key, workload token or portal JWT”There are three developer-relevant credentials:
| Credential | Header | Who uses it | How you get it |
|---|---|---|---|
| API key | Authorization: Bearer sk_brutor_api_... or X-API-Key: sk_brutor_api_... |
Agents, scripts, CLIs, backend services | Admin UI → Resource Group → API Keys, or via the Admin API |
| IdP workload token | Authorization: Bearer <OIDC or SPIFFE token> |
Agents with their own workload identity | Your IdP; the tenant’s identity provider maps its subject to an agent identity |
| Portal JWT | Authorization: Bearer <jwt> (or X-Gateway-Authorization: Bearer <jwt>) |
End users in a portal/chat client you build | POST /v1/portal/auth/login with the user’s username/password |
API keys are sk_brutor_api_ + 43 URL-safe characters, stored SHA256-hashed server-side, and bound to a resource group — for an agent, that group is its AI System, so every call and every run with the key is attributed to that system. Give the key an agent_id and every call also acts as that agent identity: default-deny, with explicit allow / approval_required / deny grants per model, tool, skill and agent card. Who is acting always comes from the credential, never from a header. Portal JWTs are HS256 tokens minted by the Core Proxy at login; they carry the user’s identity, and the user’s group memberships determine what models and tools they can reach.
Two more headers matter on multi-tenant installs:
| Header | Example | Purpose |
|---|---|---|
X-Tenant-ID |
default |
Tenant context when it can’t be derived from the credential |
X-Resource-Group |
rg-01ABC... |
Pin the request to one of the caller’s groups (defaults to primary group, then first membership) |
Assurance headers: turn calls into runs
Section titled “Assurance headers: turn calls into runs”An agent makes many gateway calls to do one task. These optional headers tell the gateway which calls belong together and which release of your code made them. Without them every call is its own run, and the run ledger has nothing to group, close or baseline.
headers = { # the run: the same id on every LLM, MCP, skill and A2A call of one task "x-brutor-run-id": run_id, "X-Brutor-Step-Id": "gather", # optional: name the phase "X-Brutor-Turn-Id": "t1", # optional: one pass of the loop # the release: which software, which version "X-Brutor-Agent-Name": "loan-screening-agent", "X-Brutor-Agent-Version": "0.2.0",}# on the final call of the taskheaders |= {"X-Brutor-Run-End": "completed", "X-Brutor-Run-Outcome": "resolved"}| Header | Example | Purpose |
|---|---|---|
x-brutor-run-id |
screen-4711-01K… |
Groups every call of one task into a run |
X-Brutor-Step-Id / X-Brutor-Step-Name |
gather |
Names a phase; the ledger attributes cost and tools per step |
X-Brutor-Turn-Id / X-Brutor-Turn-Seq |
t1 / 1 |
One pass of the agent loop; inferred from model-call boundaries when absent |
X-Brutor-Run-End |
completed · completed_degraded · exhausted · errored · blocked_policy · abandoned · cancelled |
Closes the run explicitly instead of waiting for the idle sweep |
X-Brutor-Run-Outcome |
resolved · escalated · handed_off · abandoned_by_user |
How the task ended for the user |
X-Brutor-Agent-Name / -Version / -Build |
loan-screening-agent / 0.2.0 / sha256:… |
The release, checked against the release the contract approved. See Agent implementation |
Develop your AI System explains why to send them from day one, and Build an agent §6 wires them into a LangGraph loop.
Which surface do I use?
Section titled “Which surface do I use?”I want my app or agent to call an LLM → point your OpenAI/Anthropic SDK at the gateway. One base-URL change.
I want my agent to use tools (CRM, Slack, databases…) → call registered MCP servers through /v1/proxy/mcp/{server_id}, or aggregate many behind one Virtual MCP endpoint.
I’m building a chat UI for end users → build on the Portal API: login, model pickers, streaming chat, approval inbox, notifications.
I have a REST tool of my own → wrap it as an MCP server (a few dozen lines with FastMCP), register it, and every agent in the org can use it — governed.
My agent needs to talk to other agents → publish a signed A2A agent card for inbound calls, and reach remote agents through /v1/proxy/a2a/outbound.
I want reusable, versioned automation with human approval gates → package it as an Agent Skill.
A note on scope
Section titled “A note on scope”Brutor governs traffic that points at the gateway: API calls, agents, internal apps, CLIs, portals — across every provider and resource you register. The moment a client (your agent, curl, Goose, Claude Code, a custom portal) is configured with the gateway’s URL, its traffic is governed, metered, and audited. Brutor does not — and does not claim to — govern what employees do inside the consumer ChatGPT or Claude apps themselves; that is the domain of network controls and the vendors’ own admin consoles. (Brutor can import vendor admin telemetry for cross-vendor cost observability — see Observing bought AI.)
Where to next
Section titled “Where to next”If the gateway isn’t running yet, start with the Quickstart — Docker Compose, about ten minutes. Then make your first governed LLM call and watch it appear in Mission Control. When you are building an agent, follow Your AI System from develop to assure.

