Version

Tenancy & Scopes

Every read and write is scoped to one team, scope entries on a token narrow it further to chosen projects, and a guest's token never reaches beyond their invitations.

Zuse Bifrost is multi-tenant by construction. Every project belongs to a team, and every row that hangs off a project inherits that team's isolation. The API preserves the same boundary: a token minted for Team A can never read or change rows that belong to Team B.

Request-scoped team

Every token carries a mandatory scope:team:<uuid> entry in its ability list. When a request arrives on the API host:

  1. The bearer token is validated.
  2. The server reads scope:team:<uuid> from the token's ability list and scopes every query to that team for the duration of the request. No database write happens; the override lives in memory only.

From that point on, every read the request performs is automatically tenant-scoped, the same isolation the app uses. You never pass team_id manually in a request body, except where a resource is created in a team you choose: a token, a role or a group.

Missing the scope:team:<uuid> entry on a token fails every request with TENANT_MISMATCH before the controller even runs. The settings page appends the scope automatically; only hand-crafted tokens miss it.

Optional project scope entries

To further restrict a token, attach one or more scope:project:<uuid> entries alongside the team scope. The server parses them into an allow-list; any request touching a project outside the list returns TENANT_MISMATCH (HTTP 404; see below).

Typical use cases:

  • A pipeline token that reports deployments and test results for one project only.
  • An MCP token handed to an agent working inside one project.
  • A read-only reporting pull from a subset of projects.

Members-only projects

A project can be visible to its whole team or to its project members only. The API honours the same rule as the app: a token acting as a person who is not a member of a members-only project does not see it in GET /v1/projects, and GET /v1/projects/{project} for it is a 404.

Guests

A client stakeholder invited as a guest holds no team-wide abilities. Their token is confined to the projects they were invited to whatever abilities it carries, and it must name one of those projects when minted. Everything internal stays hidden in the service layer, so a guest's token sees what the guest sees in the app and nothing more.

Cross-team access is 404, not 403

When a token with scope:team:A tries to GET /v1/projects/<id-in-team-B>, the response is:

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error": {"code": "RESOURCE_NOT_FOUND", ...}}

This is intentional: a 403 would leak the fact that the project exists under a different team. 404 keeps tenant existence private.

MCP inherits the same contract

The MCP server enforces the same team and project scoping. A token issued for Team A that tries to exercise a tool against Team B's project returns the same TENANT_MISMATCH envelope, wrapped in the MCP response shape.

How is this guide?

Last updated on

Version

On this page

What changed recentlyEvery release of Zuse Bifrost, with the pages it touched.

For AI agents: llms.txt ยท llms-full.txt