Users, Groups & API Keys
Brutor has two distinct populations of people, plus machine credentials:
| Identity | Who | Authenticates against | Used for |
|---|---|---|---|
| Admin users | Platform / tenant operators | Control Plane (:5050) |
Admin UI, Admin API, provisioning scripts |
| End users | People consuming AI (portal, chat clients) | Core Proxy (:8100, /v1/portal/auth/login) |
User Portal, runtime traffic |
| API keys | Agents, services, scripts | Core Proxy (:8100) |
Programmatic LLM/MCP/Skill traffic |
Admin users
Section titled “Admin users”Admin users come in two tiers:
- System admins sit above the tenant hierarchy (no
tenant_id) — they manage tenants themselves. Login:POST /v1/admin-users/system/login. - Tenant admins belong to one tenant and carry a role (
role_id) whose permissions gate every Admin API call. Login:POST /v1/admin-users/tenant/loginwith{username, password, tenant_id?}→{access_token, ...}. Both admin types support TOTP 2FA — checkrequires_2fain the login response before scripting against the API.

Roles and their permission sets are managed under Admin Users → Roles in the Admin UI:

End users
Section titled “End users”End users are the people whose requests the gateway governs. Create them in the Admin UI or via the API:
curl -X POST http://localhost:5050/v1/admin/end-users \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "ada", "password": "S3cure-Passw0rd!", "email": "ada@example.com", "display_name": "Ada Lovelace", "send_welcome_email": false }'{ "user": { "id": 42, "username": "ada", "email": "ada@example.com", "is_active": true, "source": "manual" }}Every end user carries a source recording how the account came to exist: manual (created by an admin), saml (JIT-provisioned at SSO login), scim (pushed by your IdP), or import.
Other useful endpoints: PATCH /v1/admin/end-users/{id}/status {"is_active": false} to deactivate, GET /v1/admin/end-users?search=ada, and CSV/JSON export under /v1/admin/end-users/export/csv|json.
End-user groups
Section titled “End-user groups”End-user groups are how people get into resource groups. You never bind individual users to guardrails or models — you put users in an end-user group, then bind the group to one or more resource groups.
# Create a group with initial memberscurl -X POST http://localhost:5050/v1/admin/end-user-groups \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "data-science", "display_name": "Data Science", "description": "DS team members", "initial_member_ids": [42, 43] }'
# Bind it to a resource groupcurl -X POST http://localhost:5050/v1/admin/resource-groups/{rg_id}/end-user-groups \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"end_user_group_ids": ["eug-01H..."]}'Membership management: POST /v1/admin/end-user-groups/{id}/members {"end_user_ids": [...]} adds, DELETE .../members removes, and PUT .../members sets the authoritative full list (useful for sync scripts).
Groups also carry a source (manual, saml, scim):
- SAML — groups asserted by your IdP at login are matched by
source_external_idand users are JIT-added. - SCIM — your IdP pushes users and groups to
/scim/v2/Usersand/scim/v2/Groupson the Control Plane, authenticated with a SCIM bearer token minted under Admin → SCIM tokens (/v1/admin/scim-tokens).
Multi-group users and the active workspace
Section titled “Multi-group users and the active workspace”A user can be a member of many resource groups (directly, or via end-user groups). Two rules govern how that resolves at runtime:
-
Access checks consider ALL groups — allow-wins. If any of the user’s groups grants a model, MCP server, or tool capability, the user has it. A capability disabled in one workspace can still be reachable through a less-restricted sibling group.
-
One group is the active group per request — it determines usage attribution, limits, and workspace context. Selection order:
X-Resource-Groupheader, if present and the user really is a member of that group (client-supplied values are validated against actual memberships);- otherwise the user’s primary group;
- otherwise the first membership.
The primary group is the user’s default workspace:
curl -X PATCH http://localhost:5050/v1/admin/end-users/42/primary-group \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"group_id": "9f2c..."}'The user must already be a member of the target group.
API keys
Section titled “API keys”API keys give agents, services, and scripts governed access to the runtime proxy without a human login. Keys are bound to a resource group — they inherit that group’s models, tools, guardrails, and limits. Keys are not inherited down the resource-group tree.
Format: sk_brutor_api_ followed by 43 URL-safe characters. Sent as Authorization: Bearer sk_brutor_api_... or X-API-Key: sk_brutor_api_....
Storage: only the SHA-256 hash is stored. The plaintext key appears exactly once — in the creation response. Capture it then or re-issue.
Scopes:
access_mode: "shared"→ stored scopeglobal: a shared key usable by the whole group — the standard choice for agents and services.access_mode: "user"→ scopeuser: tied to one end user (user_idrequired); requests attribute to that user.
Create a key
Section titled “Create a key”curl -X POST http://localhost:5050/v1/admin/resource-groups/{rg_id}/api-keys \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "revenue-sentinel-agent", "description": "Shared key for the revenue agent", "access_mode": "shared", "expires_at": "2027-01-01T00:00:00Z" }'{ "id": "apikey-8c1e...", "key_value": "sk_brutor_api_M81FDppPA5VW6rA5SNpYXhbmkqSSN574W9i-fxYajGw", "key_prefix": "sk_brutor_api_M81FDp...", "name": "revenue-sentinel-agent", "scope": "global", "access_mode": "shared", "group_id": "9f2c...", "enabled": true, "expires_at": "2027-01-01T00:00:00Z"}Lifecycle
Section titled “Lifecycle”| Operation | Endpoint | Effect |
|---|---|---|
| Disable | POST .../api-keys/{key_id}/disable |
Temporary — key rejects until re-enabled |
| Enable | POST .../api-keys/{key_id}/enable |
Undo disable |
| Revoke | POST .../api-keys/{key_id}/revoke |
Permanent — records who/when/why |
| Expiry | expires_at on the key |
null = never expires; expired keys fail auth |
At runtime the proxy accepts a key only when enabled AND NOT revoked AND (expires_at IS NULL OR expires_at > now()).
Skill restrictions
Section titled “Skill restrictions”A key can optionally be restricted to specific agent skills via allowed_skill_ids (a list of skill IDs; null means all skills the group grants) and throttled with skill_rate_limit_daily. Keys can also be bound to an agent identity (agent_id) for per-agent attribution and runtime authorization.
Usage per key is visible at GET /v1/admin/resource-groups/{rg_id}/api-keys/usage and in Mission Control.
Related pages
Section titled “Related pages”- Resource groups — what group membership actually grants
- Budgets, quotas & rate limits — what the active group’s limits do to a request
- Tool approvals — group members approve each other’s sensitive tool calls
