Skip to content

Governance Tour

This is a self-serve, ~30-minute tour of Brutor’s governance controls. You will build a small governed environment from scratch, then deliberately violate every control you set — and watch each decision arrive in the logs and Mission Control.

What you’ll set up, in order:

  1. A resource group — the unit of governance
  2. A model binding — what the group may call
  3. A user-group binding and an API key — who may call it
  4. A daily token budget and an RPM limit
  5. A PII guardrail that blocks on chat_input

Every step shows both the Admin Console clickpath and the equivalent Admin API call, so you can replay the whole tour as a script later (see setup scripts).

Prerequisites: a running gateway (quickstart) with a provider API key configured on at least one enabled model, and the seeded admin login.

Everything below uses the Control Plane admin API on :5050. Log in once and reuse the bearer token:

Terminal window
curl -s http://localhost:5050/v1/admin-users/tenant/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "Admin123!", "tenant_id": "default"}'
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600,
"admin": { "username": "admin", "...": "..." },
"session_id": "..."
}
Terminal window
export TOKEN="eyJ..." # the access_token value
export CP=http://localhost:5050
  1. Create a resource group.

    A resource group bundles resources (models, MCP servers, skills), the people allowed to use them, and the limits and policies that apply. Governance in Brutor always hangs off a resource group.

    Admin Console: Resource Groups → Create Group → name it gov-tour, type team.

    API:

    Terminal window
    curl -s -X POST $CP/v1/admin/resource-groups \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{
    "name": "gov-tour",
    "display_name": "Governance Tour",
    "description": "Scratch group for the governance tour",
    "group_type": "team",
    "inherit_resources": false
    }'
    {
    "id": "3f9c2c6e-...-...",
    "name": "gov-tour",
    "display_name": "Governance Tour",
    "group_type": "team",
    "parent_group_id": null,
    "hierarchy_level": 0,
    "inherit_resources": false,
    "...": "..."
    }
    Terminal window
    export RG="3f9c2c6e-..." # the id value
  2. Add a model to the group.

    A resource group starts with access to nothing. Find the ID of an enabled chat model, then bind it.

    Admin Console: Resource Groups → Governance TourAI Models tab → Add Model.

    Assigning AI models to a resource group

    API:

    Terminal window
    curl -s "$CP/v1/admin/llms?mode=chat" -H "Authorization: Bearer $TOKEN"
    {
    "models": {
    "b71a...-...": { "id": "b71a...-...", "name": "OpenAI GPT-5.2 Auto-Routing", "model_name": "gpt-5.2", "enabled": true, "...": "..." }
    },
    "total": 1
    }
    Terminal window
    export MODEL_ID="b71a..."
    curl -s -X POST $CP/v1/admin/resource-groups/$RG/llm-models \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d "{\"llm_model_id\": \"$MODEL_ID\"}"

    Returns 201 Created (or 409 if the binding already exists — bindings are idempotent to re-assert).

  3. Bind a user group.

    API keys give programmatic access; end-user groups give people access (portal login, user-bound keys). Bind the trial user via a fresh end-user group.

    Admin Console: Users → Groups → create tour-users with member trial, then Resource Groups → Governance TourMembers tab → add the group.

    API:

    Terminal window
    # find the seeded trial user's id
    curl -s "$CP/v1/admin/end-users?search=trial" -H "Authorization: Bearer $TOKEN"
    # create an end-user group containing them
    curl -s -X POST $CP/v1/admin/end-user-groups \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{
    "name": "tour-users",
    "display_name": "Tour Users",
    "description": "Users taking the governance tour",
    "initial_member_ids": [42]
    }'
    { "id": "7d2e...-...", "name": "tour-users", "...": "..." }
    Terminal window
    # bind the end-user group to the resource group
    curl -s -X POST $CP/v1/admin/resource-groups/$RG/end-user-groups \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{"end_user_group_ids": ["7d2e..."]}'
  4. Create an API key for the group.

    Admin Console: Resource Groups → Governance TourAPI Keys tab → Create.

    API:

    Terminal window
    curl -s -X POST $CP/v1/admin/resource-groups/$RG/api-keys \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{"name": "tour-key", "access_mode": "shared"}'
    {
    "id": "9a1f...-...",
    "key_value": "sk_brutor_api_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "key_prefix": "sk_brutor_api_XXXX",
    "name": "tour-key",
    "scope": "global",
    "access_mode": "shared",
    "group_id": "3f9c2c6e-...",
    "enabled": true,
    "...": "..."
    }

    key_value is shown only in this response — capture it now.

    Terminal window
    export KEY="sk_brutor_api_..."
  5. Set a daily token budget and an RPM limit.

    Deliberately tiny values so you can trip them in minutes: 2,000 tokens per day, 3 requests per minute.

    Admin Console: Resource Groups → Governance TourLLM Limits tab.

    Limits on a resource group

    API (this PATCH replaces the whole llm_global_limits document — always send the full desired state):

    Terminal window
    curl -s -X PATCH $CP/v1/admin/resource-groups/$RG/llm-global-limits \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{
    "llm_global_limits": {
    "tokens": { "daily_limit": 2000 },
    "throughput": { "max_requests_per_minute": 3, "cooldown_seconds": 60 }
    }
    }'

    The response echoes the stored limits document.

  6. Attach a PII guardrail.

    Create a guardrail config that blocks requests containing emails or SSNs on the chat_input surface, bound to your group.

    Admin Console: Guardrails → Create → enable PII detection, action block, categories email and ssn, surface chat input, and assign the Governance Tour group.

    Guardrail configuration form

    API:

    Terminal window
    curl -s -X POST $CP/v1/admin/guardrails/configs \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{
    "name": "tour-pii-block",
    "description": "Block email/SSN in prompts",
    "enabled": true,
    "group_ids": ["'$RG'"],
    "surfaces": { "chat_input": true },
    "builtin": {
    "pii_enabled": true,
    "pii_action": "block",
    "pii_categories": ["email", "ssn"],
    "pii_surfaces": ["chat_input"]
    }
    }'
    { "id": "c4d8...-...", "name": "tour-pii-block", "enabled": true, "...": "..." }
  7. Trip the guardrail.

    Send a prompt containing PII through the new key:

    Terminal window
    curl -s http://localhost:8100/v1/proxy/llm/chat/completions \
    -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
    -d '{
    "model": "gpt-5.2",
    "messages": [{"role": "user", "content": "Email the report to jane.doe@example.com please"}]
    }'

    The request never reaches the provider — 403 Forbidden:

    {
    "error": {
    "code": "guardrail_blocked",
    "message": "guardrail hit on `email` (1 match)",
    "guardrail": "tour-pii-block",
    "check": "pii",
    "direction": "input"
    }
    }
  8. Trip the RPM limit.

    Fire four clean requests inside one minute — the fourth exceeds max_requests_per_minute: 3:

    Terminal window
    for i in 1 2 3 4; do
    curl -s -o /dev/null -w "request $i → %{http_code}\n" \
    http://localhost:8100/v1/proxy/llm/chat/completions \
    -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
    -d '{"model": "gpt-5.2", "messages": [{"role": "user", "content": "Say ok."}]}'
    done
    request 1 → 200
    request 2 → 200
    request 3 → 200
    request 4 → 429

    The 429 body names the limit and scope, and the response carries a Retry-After header (here, your 60-second cooldown):

    {
    "error": {
    "type": "rate_limit_exceeded",
    "message": "LLM rate limit exceeded at group scope (3 requests/min) — retry in 60s",
    "scope": "group",
    "limit_type": "rpm"
    }
    }
  9. Trip the daily token budget.

    After the cooldown, send a few longer requests (e.g. “write 300 words about…”) until the group’s daily total passes 2,000 tokens. The next request is refused with limit_type: "daily_tokens":

    {
    "error": "Daily token limit exceeded: 2143 / 2000",
    "usage_limit": {
    "group_id": "3f9c2c6e-...",
    "group_name": "gov-tour",
    "limit_type": "daily_tokens",
    "current_usage": "2143",
    "max_limit": "2000"
    }
    }

    For daily limits the Retry-After header counts down to the next UTC midnight, when the window resets. And before it blocks, watch the warning arrive: run a request with curl -i once you’re past 85% of the budget and you’ll see an X-Usage-Warning response header.

  10. Watch the decisions land.

    Open the Admin Console:

    • Mission Control → Governance shows guardrail blocks and policy decisions for the period; Mission Control → Cost / Usage shows the tokens and dollars your tour just burned, attributed to gov-tour.
    • Proxy logs show every request — including the 403 guardrail block (with the check that fired) and the 429s — each with a correlation ID you can match to the X-Correlation-Id header your client received.

    Proxy logs showing per-request governance outcomes

    Audit logs record the admin actions from this tour too — every group, key, limit and guardrail you created. See the observability tour for the full read-side story.

  11. Clean up.

    Terminal window
    curl -s -X DELETE $CP/v1/admin/guardrails/configs/c4d8... -H "Authorization: Bearer $TOKEN"
    curl -s -X DELETE $CP/v1/admin/resource-groups/$RG -H "Authorization: Bearer $TOKEN"
    curl -s -X DELETE $CP/v1/admin/end-user-groups/7d2e... -H "Authorization: Bearer $TOKEN"

    Deleting the resource group revokes its API keys and bindings; the trial user itself is untouched.

Every control you tripped — the 403 guardrail_blocked, the 429 with Retry-After, the token-budget refusal — was enforced inline by the core proxy, attributed to a resource group, and recorded with a correlation ID. That is the whole governance model in miniature: resource groups carry the policy, the gateway enforces it, the ledger remembers it.