“I'm building an AI application”
Chat, streaming, embeddings, images and audio through one OpenAI-compatible API. Model routing, fallbacks and caching are configuration, not code you maintain.
Wiring an agent to Brutor takes three things: the gateway host, with its own path for each kind of AI resource, an API key bound to an agent identity that is locked down from the first call, and a handful of headers that turn separate requests into a run you can assure. Access control, guardrails, budgets, approvals and audit apply on the way through, without a line of it living in your codebase.
Every call your agent makes goes to the same host: the Brutor gateway, on port 8100. The path says which kind of AI resource you are calling, and the body stays in the dialect your client already speaks. One host, one credential, and the provider keys never reach your agent.
On your laptop the gateway is at http://localhost:8100. In any other environment, change the host and nothing else.
OpenAI-shaped calls go to /v1/proxy/llm/chat/completions. Models that require the Responses API, such as gpt-5.5, go to /v1/proxy/llm/responses. Anthropic and Gemini clients keep their native paths.
MCP calls go to /v1/proxy/mcp/{server_id}: the server's database id, not its name, with a JSON-RPC tools/call body and the bare tool name.
The skill runner is itself an MCP server. Call its skills__run_script tool on the same MCP route, and the skill runs sandboxed under the same governance.
Delegation goes to /v1/proxy/a2a/outbound with the target card id and the capability. The gateway calls the other agent for you and signs the delegation chain.
| AI resource | Path | Notes |
|---|---|---|
| Chat & completions | POST /v1/proxy/llm/chat/completions | OpenAI-compatible; SSE streaming with "stream": true |
| Responses API | POST /v1/proxy/llm/responses | Required by models flagged for it; same governance, same run headers |
| Embeddings | POST /v1/proxy/llm/embeddings | Governed and budgeted like chat |
| Models | GET /v1/proxy/llm/models | Returns your governed list, not the provider catalog |
| Images · audio · video | /v1/proxy/llm/images/generations · audio/speech · audio/transcriptions · video/generations | Media modes carry their own governance settings |
| Anthropic native | POST /v1/messages | No /proxy/ prefix, vendor dialect preserved |
| Gemini native | /v1beta/* | Google's URL convention, unchanged |
| MCP tools | POST /v1/proxy/mcp/{server_id} | Streamable HTTP; JSON-RPC tools/call with the bare tool name |
| Virtual MCP | /v1/proxy/vmcp/{id} | Many servers behind one connection for single-connection clients |
| Skills | /v1/proxy/mcp/{skill_server_id} · /v1/proxy/skills | Progressive disclosure: list, load, then run on demand |
| Agent-to-agent, outbound | POST /v1/proxy/a2a/outbound | Target card id, capability and message; the gateway signs the chain |
| Agent-to-agent, inbound | /a2a/{tenant}/{agent}/message:send | A2A v1.0: signed cards, SSE streaming |
| Runs | POST /v1/runs/{root}/end | Close a run outside a call (see 03) |
| Approvals | GET /v1/portal/approvals/{id}/poll | Poll a held call with the same API key |
| Portal API | /v1/portal/* | End-user surfaces: auth, conversations, KBs, approvals |
The full contract is machine-readable: Swagger UI at /docs, OpenAPI 3.1 at /api-docs/openapi.json. Generate a client instead of reading prose.
An admin mints one API key per AI System and binds it to an agent identity. The key decides attribution: every call, and so every run, belongs to that key's AI System. The identity is the actor: default deny, with explicit grants per model, tool, skill and agent card. Who is acting never comes from a header.
Minted by an admin under the AI System's API Keys tab and shown once. Revoke or regenerate it at any time; its blast radius is that one AI System.
Set the key's agent identity and every call with that key acts as the agent. An agent that holds a workload token from Okta, Entra Agent ID or SPIFFE can present it instead: Brutor maps the subject to the identity and does not issue it.
A grant names an action type (llm_call, mcp_tool, skill_exec, a2a_call), a target glob and an effect: allow, approval_required or deny. The strictest matching grant wins, and the AI System's own access rules must agree.
Default deny on, a named human owner. With no grants it can do nothing at all.
One grant per model, tool, skill and card the agent calls. Use approval_required for the actions a person must sign off.
Mint the AI System's key with the identity's agent_id. The provider keys stay encrypted in the control plane.
Enforce from the first call, or start in dry_run to record would-block decisions while traffic proceeds, tune the grants, then flip to enforce.
403 agent_not_authorized, a policy-lookup failure fails closed, and a revoked or suspended identity is refused even in dry run. Tool risk classes come from MCP tool annotations, and a tool with none counts as destructive, so deny_tool_risk: ["destructive"] blocks unannotated tools too. All optional, all worth sending. Without them every call is its own run, and the run ledger has nothing to group, close or baseline. With them, nine calls become one run with steps, turns, an outcome and a release, and that run is what every assurance verdict in Brutor is built on.
| Send | Header | Value |
|---|---|---|
| Run · every call | x-brutor-run-id | One opaque id per task (128 characters at most), identical on every LLM, MCP, skill and A2A call of the run. Bound to your credential, so it cannot collide with another principal's runs. |
| Run · every call | X-Brutor-Turn-Id · X-Brutor-Turn-Seq | The turn, for example t1, t2 |
| Run · every call | X-Brutor-Step-Id · X-Brutor-Step-Name | The phase of your loop, for example gather, assess, decide. Named by you, never inferred. |
| Run · every call | traceparent | W3C trace context, one trace per run |
| Close · last call | X-Brutor-Run-End | A literal state: completed, completed_degraded, errored, blocked_policy, cancelled, exhausted, abandoned |
| Close · last call | X-Brutor-Run-Outcome | resolved, escalated, handed_off, abandoned_by_user; kept only with Run-End on the same request |
| Release · every call | X-Brutor-Agent-Name · X-Brutor-Agent-Version · X-Brutor-Agent-Build | Which software sent the call, and which release (the build, such as a git SHA, when set). Recorded as declared; it never changes an access decision. |
| MCP · each tools/call | params._meta clientInfo | The same name and version as { name, version }, the standard MCP carrier |
| Delegate · its own calls | x-brutor-delegation-* | An agent called over A2A echoes the six delegation headers it received (root, parent, depth, sig, actor, subject) unchanged and sends no run id of its own. Its work lands in the caller's run. |
No close sent? The run is swept as abandoned after the AI System's idle window. You can also close it with POST /v1/runs/{root}/end, where the root is the x-brutor-run-id response header of a model call.
| Status | What comes back |
|---|---|
200 | The result. Model calls also return the x-brutor-run-id response header: the derived root of the run. |
202 | Approval required: an approval_request_id, a poll_url and a timeout. A hold, not an error. Poll, then retry with X-Approval-Token. |
403 | A guardrail or policy block, with the reason. It will not succeed on retry. |
429 | A usage limit or run cap, retry-after included |
5xx | Upstream provider fault: the gateway has already tried fallbacks first |
Each lane has a written guide that ends with working code, and most have a video series showing the governed result from the operator's side, useful for knowing what happens when your call is blocked, throttled or held for approval.
Chat, streaming, embeddings, images and audio through one OpenAI-compatible API. Model routing, fallbacks and caching are configuration, not code you maintain.
Register it once and it becomes a governed tool surface: per-tool access, argument policies, OAuth handled by the gateway rather than by every client separately.
Every URL in the agent's config points at the gateway: LLM, tools and agent-to-agent alike. One governed loop, and the run ledger groups all of it into tasks you can reason about.
Versioned, sandboxed capabilities with progressive disclosure: models see a preamble first and load the full skill on demand. Only what SKILL.md declares can run.
The Portal API is the same one Brutor's own User Portal runs on: end-user auth, conversations, models, knowledgebases and approvals. Build your own front end on a governed back end.
Knowledgebases with 8 connectors (Confluence, Jira, SharePoint, Slack, Notion, GitHub, Google Drive, web crawl) and hybrid retrieval, all behind the same access model as everything else.
Coding agents, IDE assistants, chat UIs and frameworks don't need plugins or adapters. They need the gateway's URL where the provider's used to be.
Set ANTHROPIC_BASE_URL and a Brutor key. Model calls are governed; MCP servers added via the gateway URL bring tool governance too.
Set OPENAI_BASE_URL to the proxy path. The models the tool can see become exactly the models you're allowed to use.
Add an MCP endpoint of the form /v1/proxy/mcp/{server_id} and tools arrive pre-filtered by your per-tool grants.
The stock OpenAI integration works unmodified: pass the gateway base URL to the client constructor and chains inherit routing, budgets and audit.
Point its OpenAI connection at the gateway to give a whole team a familiar chat UI on governed rails.
Anything that can speak HTTPS to OpenAI, Anthropic, Gemini, MCP or A2A can speak it to the gateway. That is the point.
Guides for every lane on this page, plus references generated from the same OpenAPI contract the gateway serves.
Short, real-console walkthroughs: see what the operator sees when your code runs, including the parts where it gets blocked.
A hands-on Udemy course on building MCP servers properly, from first tool to OAuth, testing and registration behind a gateway.
Download the full stack, point your existing code at it, and watch your own traffic arrive governed, on your machine, this afternoon.