5.4.2.2 Staff Access
The Staff Access domain of access. Moonitoring users (people and service accounts), the staff roles and their memberships, and the catalog of capabilities every system publishes. Entirely separate from Organization access: nothing here references a User or an Organization. It is one API, with its own contract and its own version.
5.4.2.2.1 Versions¶
| Version | Status | Prod URL | Contract |
|---|---|---|---|
v1 |
Proposed | https://access.moonitoring.com/staff/v1 |
staff/v1/openapi.yaml |
The resources follow the data model. The CapabilityCatalog schema has no entity in the data model: it is what each system publishes from its code (req 1002, 1006 notes) and the union access validates roles against.
5.4.2.2.2 Operations¶
Every operation names who calls it and the capability it checks (x-moonitoring-audience, x-moonitoring-capability in the contract); every capability is in the catalog of the data model (req 1002, 1006). Lists are paged by cursor, writes that create something carry an Idempotency-Key, errors are Problem Details.
| Operation | Request | Who | Capability |
|---|---|---|---|
| Who am I | GET /me |
Staff | none |
| List Moonitoring users | GET /users |
Staff | access.moonitoring_access.read |
| Create a Moonitoring user | POST /users |
Staff | access.moonitoring_access.manage |
| Read a Moonitoring user | GET /users/{user_id} |
Staff | access.moonitoring_access.read |
| Disable a Moonitoring user | POST /users/{user_id}/disable |
Staff | access.moonitoring_access.manage |
| Link a Google service account | PUT /users/{user_id}/google-service-account |
Staff | access.moonitoring_access.manage |
| Unlink the Google service account | DELETE /users/{user_id}/google-service-account |
Staff | access.moonitoring_access.manage |
| List a user's staff memberships | GET /users/{user_id}/memberships |
Staff | access.moonitoring_access.read |
| Give a staff role | POST /users/{user_id}/memberships |
Staff | access.moonitoring_access.manage |
| Revoke a staff role | POST /users/{user_id}/memberships/{membership_id}/revoke |
Staff | access.moonitoring_access.manage |
| List staff roles | GET /roles |
Staff | access.moonitoring_access.read |
| Create a staff role | POST /roles |
Staff | access.moonitoring_roles.manage |
| Update a staff role | PATCH /roles/{role_id} |
Staff | access.moonitoring_roles.manage |
| List the capability catalogs | GET /capability-catalogs |
Staff | access.moonitoring_access.read |
5.4.2.2.3 gRPC¶
Not a service of access but one every system implements in package moonitoring.common.catalog.v1: access calls it to validate the capabilities of a role, entitlements the feature of an entitlement, moochat the capabilities a tool requires.
The contract is catalog.proto, package moonitoring.common.catalog.v1:
// Proposed contract, version 1. Status: proposal for review, not confirmed design.
//
// Every system implements this service once: it publishes, from its code, the capabilities its roles may contain,
// the entitlement features it offers and the agent tools its MCP server exposes.
// Callers: access (validates a role's capabilities against the union of the catalogs, req 1002, 1006),
// farm (validates an entitlement's feature, req 68.3), moochat (validates a tool's required capabilities, req 122).
// Deadline: 2 s. Cached by the caller; refreshed after a deploy.
// The callers listed are authentication only (decided 2026-11-11): each RPC below names who may call it and what
// it sees, and refuses anyone else with PERMISSION_DENIED.
// x-moonitoring-callers: access, farm, moochat
// x-moonitoring-requirements: 1002, 1006, 68.3, 122, 141, 144.8
syntax = "proto3";
package moonitoring.common.catalog.v1;
import "google/protobuf/timestamp.proto";
// The catalog a system publishes from its code.
service CatalogService {
// Returns the catalog of the system that serves the call.
// Callers: access, farm and moochat. Sees: the same catalog for every caller; it holds no customer data.
rpc GetCatalog(GetCatalogRequest) returns (GetCatalogResponse);
}
message GetCatalogRequest {
// When set, the server answers NOT_MODIFIED semantics by returning an empty catalog with the same version.
string known_version = 1;
}
message GetCatalogResponse {
Catalog catalog = 1;
}
message Catalog {
// Short name of the system, e.g. farm.
string system = 1;
// Release of the system that published it, e.g. 1.4.0.
string version = 2;
// Capabilities for Organization roles (req 1002), named <system>.<resource>.<action>.
repeated Capability organization_capabilities = 3;
// Capabilities for staff roles (req 1006).
repeated Capability staff_capabilities = 4;
// Entitlement features (req 68.3), named <system>.<feature>.
repeated Feature features = 5;
// Agent tools of the system's MCP server (req 122).
repeated Tool tools = 6;
google.protobuf.Timestamp published_at = 7;
// Event types the system lets leave the platform, offered by the Webhooks API (req 141.4, 144.8).
repeated ExternalEventType external_events = 8;
}
message ExternalEventType {
// Type with its version, e.g. calving.registered.v1.
string type = 1;
string description = 2;
// True when a newer version replaces it; it is still delivered to the subscriptions that chose it.
bool deprecated = 3;
}
message Capability {
string name = 1;
string description = 2;
// As req 1002 and 1006 give it. access derives from it what a delegation, a join link and a service account may
// hold, and which actions ask for a fresh sign-in; it refuses a catalog with an unspecified risk.
CapabilityRisk risk = 3;
}
enum CapabilityRisk {
CAPABILITY_RISK_UNSPECIFIED = 0;
// Only reads.
CAPABILITY_RISK_READ = 1;
// Changes the data of an Organization or of the platform.
CAPABILITY_RISK_WRITE = 2;
// Changes who can act, where data goes or whether something exists: never delegated, never through a join link,
// never held by a service account.
CAPABILITY_RISK_ADMIN = 3;
}
message Feature {
string name = 1;
string description = 2;
// Whether FeatureUsage is kept for it (req 69).
bool metered = 3;
// The keys an entitlement's limits may set, e.g. tokens_per_month.
repeated string limit_keys = 4;
}
message Tool {
// Namespaced like a capability: <system>.<tool>, e.g. farm.animals.search.
string name = 1;
string description = 2;
// The MCP server and tool name that implement it.
string mcp_server = 3;
ToolRisk risk = 4;
DataClass data_class = 5;
repeated string required_capabilities = 6;
repeated string required_features = 7;
string version = 8;
}
enum ToolRisk {
TOOL_RISK_UNSPECIFIED = 0;
TOOL_RISK_READ = 1;
TOOL_RISK_WRITE = 2;
TOOL_RISK_DANGEROUS = 3;
}
enum DataClass {
DATA_CLASS_UNSPECIFIED = 0;
DATA_CLASS_PUBLIC = 1;
DATA_CLASS_PERSONAL = 2;
}
5.4.2.2.4 Contract v1¶
Proposed version
v1 is a proposal for review: its endpoints, errors and capabilities were designed from the requirements and the flows and are not confirmed. It leaves proposal with its first release; from then on it changes only as described in Every API Is Versioned.
5.4.2.2.5 Administrators and Staff Roles¶
No member of staff changes their own access, and the top role is never granted through the API (decided 2026-11-10), as large platforms separate who holds power from who grants it:
Administratorsis created only by a job, never by an operation of this API:access-bootstrap-admin, a Cloud Run job ofaccessin each environment, run on request by a member ofinfra-adminswith their own identity, names the person and writes an audit event with who ran it. The same job removes an administrator. There is no other way to become one; in prod it is a privileged action with a second person's approval.- Priorities: Administrators 1000, every other predefined staff role 500 (decided 2026-11-11). No staff role but Administrators outranks another by priority alone, so among the other roles nobody grants, edits or revokes: only Administrators do.
- A membership made by the job has no
assigner: the Console shows "access-bootstrap-admin job" as who gave it and reads who ran the job from its audit event, whoseactor_systemisaccess-bootstrap-adminand whoseafternames theoperator. - Administrators manage the rest from the Console: they register staff people, grant and revoke staff roles other than
Administrators, and create and edit those roles. - Nobody acts on themselves: a member of staff cannot grant, revoke or edit a role they hold, nor change their own staff membership; the operation answers 403
self-action. - Nobody grants more than they have: a role can be granted, edited or given a capability only by someone who holds every capability in it and outranks it by priority; otherwise 403
role-capabilities-not-heldorrole-outranks-you. Granting or revokingAdministratorsthrough the API answers 403administrators-only-by-job. - Each refusal has its own problem type (decided 2026-11-11), from the common catalog (
ProblemType), so the Console says why instead of a bare 403. - Every change is an audit event, and the Console shows who made it.
The predefined staff roles (decided 2026-11-10) each cover one kind of work, with a stable key and an explicit list of capabilities: Support, Data operations, Releases, Account, Privacy, Incident, News editor, News publisher, Grant curator, ML and Agents, listed with their capabilities in MoonitoringPredefinedRoleEnum. Administrators hold every capability only so that they can grant the others. A staff service account holds no admin capability: the training account reads with farm.canonical_data.read_all and ai-model-platform.datasets.read, the CI account only uploads builds.
5.4.2.2.6 Service Accounts That Sign In Through IAP¶
A staff service account can sign in with a Google identity instead of a key (decided 2026-11-13). It is how the CI uploads client builds: the CI's Google service account passes Identity-Aware Proxy on the Console's host with a Google ID token, console-server exchanges the IAP assertion at POST /tokens/v1/tokens (grant urn:moonitoring:iap, the audience of the Console's IAP backend), and access issues the staff token of the MoonitoringServiceAccount linked to that Google service account. The token carries that account's staff roles and nothing more; no key or secret exists for it, and no other IAP audience is accepted.
- The link is the account's
google_service_account(MoonitoringServiceAccount): the e-mail of a Google service account (…gserviceaccount.com), never a person's, kept in lower case and unique among enabled service accounts. An assertion with a Google service account's e-mail is matched only against these links, any other e-mail only against MoonitoringPersons, so the two can never be confused. - Staff holding
access.moonitoring_access.manageset and clear it through the Console, withPUTandDELETE /users/{user_id}/google-service-account. Each change is an audit event,access.moonitoring_access.link_google_service_accountoraccess.moonitoring_access.unlink_google_service_account, with the e-mail before and after. - A disabled account, or one with no link, signs in with nothing: the exchange answers
invalid_grant. After an unlink or a disable, tokens already issued expire within their five minutes. - The account still holds no
admincapability, like every staff service account. Its roles are given like anyone's, by staff who outrank them; for the CI that is a role withreleases.builds.uploadandreleases.releases.readand nothing else (decided 2026-11-13), so staging a build on a channel stays with people.