5.4.2.1 Organizations
The Organizations domain of access. Organizations (farms, cooperatives, insurers and professionals), their users, memberships, user groups, delegations, cooperative constraints, the global Organization roles and the nations. Organization users act inside their own Organization; staff read every Organization and maintain the global roles. It is one API, with its own contract and its own version.
Every Organization is one of four types, Farm, Cooperative, Insurer or Professional (decided 2026-11-11), chosen in createOrganization and never changed. Each role says which types offer it and which receive it through a delegation (organization_types, delegatee_types): a membership, a group role, an invitation, a join link or a service account gets only a role its Organization's type offers, and access answers 422 otherwise. Only a Farm delegates, and only to a Cooperative, an Insurer or a Professional, with a role the delegatee's type receives; a Farm never creates a share code.
A refusal of who may grant what has its own problem type (decided 2026-11-11), still a 403, so an app can say why: self-action when someone acts on their own memberships or on a role they hold, role-outranks-you when the role's priority is not below theirs, and role-capabilities-not-held when the role holds a capability they lack (req 6.4). The types are in the common catalog (ProblemType).
User groups give roles to many users at once, like Google Workspace groups: a member holds every role of the group, in its own Organization or, through a delegation, in the delegator. access expands groups when it publishes membership.granted.v1 and membership.revoked.v1, so the other systems keep the same cached copy of memberships and never resolve a group themselves.
5.4.2.1.1 Versions¶
| Version | Status | Prod URL | Contract |
|---|---|---|---|
v1 |
Proposed | https://access.moonitoring.com/organizations/v1 |
organizations/v1/openapi.yaml |
The resources follow the data model. The contract adds input schemas (OrganizationInput, AddMemberRequest, ServiceAccountInput, DelegationInput, DelegationRoleChange, ShareCodeInput) and the views CurrentUser, MembershipView and DelegatedOrganizationView, which join a membership with its user and role for the Organization Dashboard. CooperativeLinkRequest is transient state, not an entity: once the cooperative accepts, the link is the farm's cooperative field (D8). A ShareCode is internal state too (decided 2026-11-11): since there is no directory or search of Organizations, a farm shares with its own cooperative directly and with any other Organization through a short code that Organization created; its rules are with the delegations in the data model. A delegation, a farm's cooperative and a share code carry the OrganizationSummary of the Organizations they name, and a delegation the name of its creator (PersonName), so either side reads who the other is without being its member. Nations are maintained under access.nations.manage (req 1006.39, D10).
5.4.2.1.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 |
Organization users, staff or clients | none: any member of the Organization |
| Update my profile | PATCH /me |
Organization users, staff or clients | none: any member of the Organization |
| List Organizations | GET /organizations |
Staff | access.organizations.read |
| Create an Organization | POST /organizations |
Organization users | none: any member of the Organization |
| Count Organizations | GET /organizations/counts |
Staff | access.organizations.read |
| Read an Organization | GET /organizations/{organization_id} |
Organization users, staff or clients | none: any member of the Organization or access.organizations.read |
| Update an Organization | PATCH /organizations/{organization_id} |
Organization users | access.organization.update |
| Get the closure of an Organization | GET /organizations/{organization_id}/closure |
Organization users, staff or clients | access.organizations.read or access.organization.close |
| Close an Organization | POST /organizations/{organization_id}/closure |
Organization users, staff or clients | access.organization.close or access.organizations.close |
| Cancel a closure | POST /organizations/{organization_id}/closure/cancel |
Organization users, staff or clients | access.organization.close or access.organizations.close |
| Hold a closure | POST /organizations/{organization_id}/closure/hold |
Staff | access.organizations.close |
| Approve, suspend or reactivate an Organization | POST /organizations/{organization_id}/status |
Staff | access.organizations.approve |
| List cooperative link requests | GET /organizations/{organization_id}/cooperative-link-requests |
Organization users | access.cooperative_links.manage or access.members.read |
| Ask to join a cooperative | POST /organizations/{organization_id}/cooperative-link-requests |
Organization users | access.cooperative_links.manage |
| Accept a farm into the cooperative | POST /cooperative-link-requests/{request_id}/accept |
Organization users | access.cooperative_links.manage |
| Reject or withdraw a link request | POST /cooperative-link-requests/{request_id}/reject |
Organization users | access.cooperative_links.manage |
| End the cooperative link | POST /organizations/{organization_id}/cooperative/leave |
Organization users | access.cooperative_links.manage |
| List the member farms of a cooperative | GET /organizations/{organization_id}/member-farms |
Organization users | access.cooperative_members.read |
| Transfer the ownership | POST /organizations/{organization_id}/transfer-ownership |
Organization users | access.organization.update |
| List members | GET /organizations/{organization_id}/members |
Organization users, staff or clients | access.members.read or access.organizations.read |
| List invitations | GET /organizations/{organization_id}/invitations |
Organization users, staff or clients | access.members.read or access.organizations.read |
| Invite a person | POST /organizations/{organization_id}/invitations |
Organization users | access.members.manage |
| Resend an invitation | POST /organizations/{organization_id}/invitations/{invitation_id}/resend |
Organization users | access.members.manage |
| Revoke an invitation | POST /organizations/{organization_id}/invitations/{invitation_id}/revoke |
Organization users | access.members.manage |
| Preview an invitation | POST /invitations/preview |
Organization users, staff or clients | none: any member of the Organization |
| Accept an invitation | POST /invitations/accept |
Organization users, staff or clients | none: any member of the Organization |
| List join links | GET /organizations/{organization_id}/join-links |
Organization users, staff or clients | access.members.read or access.organizations.read |
| Create a join link | POST /organizations/{organization_id}/join-links |
Organization users | access.members.manage |
| Revoke a join link | POST /organizations/{organization_id}/join-links/{link_id}/revoke |
Organization users | access.members.manage |
| List join requests | GET /organizations/{organization_id}/join-requests |
Organization users, staff or clients | access.members.read or access.organizations.read |
| Approve a join request | POST /organizations/{organization_id}/join-requests/{request_id}/approve |
Organization users | access.members.manage |
| Reject a join request | POST /organizations/{organization_id}/join-requests/{request_id}/reject |
Organization users | access.members.manage |
| Preview a join link | POST /join-links/preview |
Organization users, staff or clients | none: any member of the Organization |
| Join through a link | POST /join-links/join |
Organization users, staff or clients | none: any member of the Organization |
| Revoke a membership | POST /organizations/{organization_id}/members/{membership_id}/revoke |
Organization users | access.members.manage |
| Change a member's role | POST /organizations/{organization_id}/members/{membership_id}/role |
Organization users | access.members.manage |
| List service accounts | GET /organizations/{organization_id}/service-accounts |
Organization users, staff or clients | access.members.read or access.organizations.read |
| Create a service account | POST /organizations/{organization_id}/service-accounts |
Organization users | access.members.manage |
| Disable a service account | POST /organizations/{organization_id}/service-accounts/{user_id}/disable |
Organization users | access.members.manage |
| Rotate a service account credential | POST /organizations/{organization_id}/service-accounts/{user_id}/rotate-credential |
Organization users | access.members.manage |
| List delegations | GET /organizations/{organization_id}/delegations |
Organization users, staff or clients | access.members.read or access.organizations.read |
| Delegate a role to another Organization | POST /organizations/{organization_id}/delegations |
Organization users | access.organization_delegations.manage |
| Revoke a delegation | POST /organizations/{organization_id}/delegations/{delegation_id}/revoke |
Organization users | access.organization_delegations.manage |
| Change the role of a delegation | POST /organizations/{organization_id}/delegations/{delegation_id}/role |
Organization users | access.organization_delegations.manage |
| List share codes | GET /organizations/{organization_id}/share-codes |
Organization users | access.organization_delegations.manage |
| Create a share code | POST /organizations/{organization_id}/share-codes |
Organization users | access.organization_delegations.manage |
| Revoke a share code | POST /organizations/{organization_id}/share-codes/{share_code_id}/revoke |
Organization users | access.organization_delegations.manage |
| Preview a share code | POST /organizations/{organization_id}/share-codes/preview |
Organization users | access.organization_delegations.manage |
| Assign a delegated membership | POST /organizations/{organization_id}/delegations/{delegation_id}/members |
Organization users | access.members.manage |
| List user groups | GET /organizations/{organization_id}/groups |
Organization users, staff or clients | access.groups.read or access.organizations.read |
| Create a user group | POST /organizations/{organization_id}/groups |
Organization users | access.groups.manage |
| Get a user group | GET /organizations/{organization_id}/groups/{group_id} |
Organization users, staff or clients | access.groups.read or access.organizations.read |
| Rename or describe a user group | PATCH /organizations/{organization_id}/groups/{group_id} |
Organization users | access.groups.manage |
| Archive a user group | POST /organizations/{organization_id}/groups/{group_id}/archive |
Organization users | access.groups.manage |
| List group members | GET /organizations/{organization_id}/groups/{group_id}/members |
Organization users, staff or clients | access.groups.read or access.organizations.read |
| Add a group member | POST /organizations/{organization_id}/groups/{group_id}/members |
Organization users | access.groups.manage |
| Remove a group member | POST /organizations/{organization_id}/groups/{group_id}/members/{group_membership_id}/revoke |
Organization users | access.groups.manage |
| List group roles | GET /organizations/{organization_id}/groups/{group_id}/roles |
Organization users, staff or clients | access.groups.read or access.organizations.read |
| Give a role to a group | POST /organizations/{organization_id}/groups/{group_id}/roles |
Organization users | access.groups.manage |
| Revoke a group role | POST /organizations/{organization_id}/groups/{group_id}/roles/{assignment_id}/revoke |
Organization users | access.groups.manage |
| List cooperative constraints | GET /organizations/{organization_id}/constraints |
Organization users | access.cooperative_constraints.read |
| Create a cooperative constraint | POST /organizations/{organization_id}/constraints |
Organization users | access.cooperative_constraints.manage |
| Update a cooperative constraint | PATCH /organizations/{organization_id}/constraints/{constraint_id} |
Organization users | access.cooperative_constraints.manage |
| Delete a cooperative constraint | DELETE /organizations/{organization_id}/constraints/{constraint_id} |
Organization users | access.cooperative_constraints.manage |
| List the Organization roles | GET /roles |
Organization users, staff or clients | none: any member of the Organization |
| Create an Organization role | POST /roles |
Staff | access.organization_roles.manage |
| Read an Organization role | GET /roles/{role_id} |
Organization users, staff or clients | none: any member of the Organization |
| Update an Organization role | PATCH /roles/{role_id} |
Staff | access.organization_roles.manage |
| List nations | GET /nations |
Anyone | none |
| Create a nation | POST /nations |
Staff | access.nations.manage |
| Update a nation | PATCH /nations/{nation_id} |
Staff | access.nations.manage |
Proposed operations
Two operations are a proposal of 2026-11-10, from the roles and capabilities review:
changeMemberRolegives a member another role: the direct membership ends and a new one starts in one transaction, under the rule of req 6.4 for both roles. It needsaccess.members.manage, already in the catalog.listCooperativeMemberFarmsshows a cooperative the membership data its farms share without a delegation. Its capabilityaccess.cooperative_members.readis in the catalog since 2026-11-11 (req 1002.37), held by the predefined Owner and Manager, so a cooperative sees its member farms; the data model still has no address for an Organization, and howaccessholds the number of animals and the last data offarmis not decided.
The cooperative link uses access.cooperative_links.manage (req 1002.34) since 2026-11-10, no longer access.organization.update. Roles carry a stable key (req 6.6): Owner and Sync Client are system roles that updateOrganizationRole refuses.
GET /me names every Organization of the person (decided 2026-11-11): each MembershipView carries an organization_summary with the Organization's id, name, type (Farm, Cooperative, Insurer or Professional) and status. A person in several Organizations signs in with no active Organization, and reading an Organization otherwise needs a token switched to it; with the summary every app lists the Organizations to choose from, and the sync client and the installer list the farms, from /me alone.
GET /me keeps who the person acts as apart from the farms they can work on (decided 2026-11-11). memberships holds only the person's own Organizations, direct and through user groups, and the Organization choice after sign-in uses only these: a delegated membership is never an identity to choose. delegated_organizations lists the farms that delegated to the token's active Organization, each with its summary and the person's delegated role; a person acting as a cooperative, an insurer or a professional picks one of them as the subject of their work, and the token stays the delegatee's (Choosing the Organization).
5.4.2.1.3 gRPC¶
Every system calls StreamSnapshot to bootstrap or repair its cached copy of memberships; farm calls RegisterServiceAccount when a farm sync client redeems its enrolment code. The list of callers is authentication only (decided 2026-11-11): StreamSnapshot gives staff roles and memberships only to the backend of a system, which serves its staff operations, and to its sweeper, which fills and repairs the copies; never to a consumer or a relay; and RegisterServiceAccount and DisableServiceAccount answer only farm; the .proto names on every RPC who may call it and what it sees.
Within farm, each RPC admits only the components that need it (decided 2026-11-13). The backend registers a sync client's account when it redeems an enrolment code, and disables it when the client is retired or reinstalled. The sweeper may only disable: it retries a disable the backend could not finish, farm's own repair, and never registers. Both reach only the accounts farm registered; any other component of farm is refused with PERMISSION_DENIED.
| Caller (component, identity) | RPCs |
|---|---|
farm backend, sa-farm-api |
RegisterServiceAccount, DisableServiceAccount |
farm sweeper, sa-farm-sweeper |
DisableServiceAccount |
| Service | RPC | Callers |
|---|---|---|
OrganizationsService |
StreamSnapshot |
farm, moochat, grant, releases, ai-model-platform, ai-gateway, news |
OrganizationsService |
RegisterServiceAccount |
farm |
OrganizationsService |
DisableServiceAccount |
farm |
The contract is organizations.proto, package moonitoring.access.organizations.v1:
// Proposed contract, version 1. Status: proposal for review, not confirmed design.
//
// Synchronous calls other systems make to access about Organizations and identities. Rights are read by every
// system from its own cached copy of memberships, fed by the events of access; this service bootstraps and
// repairs that copy, and lets farm mint the identity of a farm sync client.
// Callers: every system (StreamSnapshot, at start-up or after a gap in the events; req 5, 8),
// farm (RegisterServiceAccount from its backend when an enrolment code is redeemed; DisableServiceAccount from its
// backend when a sync client is retired or reinstalled, and from its sweeper when it retries a disable the backend
// could not finish; req 67, 1.*.2).
// 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.
// Deadline: StreamSnapshot 60 s, RegisterServiceAccount 5 s, DisableServiceAccount 5 s.
// x-moonitoring-callers: farm, moochat, grant, releases, ai-model-platform, ai-gateway, news
// x-moonitoring-requirements: 5, 8, 6, 1.*.2, 67
syntax = "proto3";
package moonitoring.access.organizations.v1;
import "google/protobuf/timestamp.proto";
service OrganizationsService {
// Streams the current image of Organizations, memberships, delegations and roles, as of one write of access.
// The caller replaces its cache with the snapshot, then applies the events published after `as_of`.
// Callers: every system of the service's list, from any of its components (backend, consumer, sweeper).
// Sees: Organizations, memberships, delegations and Organization roles. Staff roles and staff memberships
// (SNAPSHOT_KIND_STAFF) only to the backend of a system, the component that serves its staff operations, and to its sweeper, which fills and repairs the copies; empty
// `kinds` means every kind the caller may receive, and asking for staff without that right is PERMISSION_DENIED.
rpc StreamSnapshot(StreamSnapshotRequest) returns (stream StreamSnapshotResponse);
// Creates a service account of an Organization with a public key and gives it a role, for a program that
// calls our API on the Organization's behalf. Used for farm sync clients at enrolment.
// x-moonitoring-callers: farm
// Callers: farm only, its backend, which redeems enrolment codes. Sees: the account it created. access refuses a
// role other than Sync Client (key sync_client) and an Organization that is not a Farm.
rpc RegisterServiceAccount(RegisterServiceAccountRequest) returns (RegisterServiceAccountResponse);
// Disables a service account that the caller registered, with its credential and its memberships, so no token is
// issued to it again and its refresh tokens stop working (decided 2026-11-05). Used by farm when a sync client is
// retired, and for the previous credential when a client is reinstalled. Idempotent: disabling a disabled account
// returns it unchanged. Refused with PERMISSION_DENIED for an account another system registered.
// x-moonitoring-callers: farm
// Callers: farm only, its backend, which retires and reinstalls sync clients, and its sweeper, which retries a
// disable the backend could not finish (decided 2026-11-13); any other component of farm is refused with
// PERMISSION_DENIED. Sees: only the accounts farm registered.
rpc DisableServiceAccount(DisableServiceAccountRequest) returns (DisableServiceAccountResponse);
}
message StreamSnapshotRequest {
// Only this Organization; empty streams every Organization.
string organization_id = 1;
// Which records to include; empty means all.
repeated SnapshotKind kinds = 2;
}
enum SnapshotKind {
SNAPSHOT_KIND_UNSPECIFIED = 0;
SNAPSHOT_KIND_ORGANIZATIONS = 1;
SNAPSHOT_KIND_MEMBERSHIPS = 2;
SNAPSHOT_KIND_DELEGATIONS = 3;
SNAPSHOT_KIND_ROLES = 4;
// Staff roles and the staff memberships that give them: every system checks staff capabilities against its copy.
SNAPSHOT_KIND_STAFF = 5;
}
message StreamSnapshotResponse {
// The point in time the snapshot is consistent with: the event id of the last event included.
string as_of_event_id = 1;
google.protobuf.Timestamp as_of = 2;
oneof record {
Organization organization = 3;
Membership membership = 4;
Delegation delegation = 5;
OrganizationRole role = 6;
StaffRole staff_role = 7;
StaffMembership staff_membership = 8;
}
}
// A staff role (MoonitoringRoleAccessControl), as staff_role.updated.v1 carries it.
message StaffRole {
string id = 1;
string name = 2;
repeated string capabilities = 3;
int32 priority = 4;
}
// An active staff membership (MoonitoringRoleMembership), as staff_membership.changed.v1 carries it.
message StaffMembership {
string id = 1;
// The MoonitoringUser.
string user_id = 2;
string role_id = 3;
// A person or a MoonitoringServiceAccount, so that rules that need a person, such as the second approver of a
// release, can tell them apart (decided 2026-11-05).
AccountKind account_kind = 4;
}
enum AccountKind {
ACCOUNT_KIND_UNSPECIFIED = 0;
ACCOUNT_KIND_PERSON = 1;
ACCOUNT_KIND_SERVICE_ACCOUNT = 2;
}
message Organization {
string id = 1;
string name = 2;
OrganizationType type = 3;
// IANA time zone of a farm, e.g. Europe/Rome.
string timezone = 4;
// The cooperative of a farm, when it has one.
string cooperative_id = 5;
string owner_user_id = 6;
// Its lifecycle (req 2.7), so a copy knows which Organizations take writes (decided 2026-11-05).
OrganizationStatus status = 7;
}
enum OrganizationStatus {
ORGANIZATION_STATUS_UNSPECIFIED = 0;
ORGANIZATION_STATUS_PENDING = 1;
ORGANIZATION_STATUS_ACTIVE = 2;
ORGANIZATION_STATUS_SUSPENDED = 3;
ORGANIZATION_STATUS_CLOSING = 4;
ORGANIZATION_STATUS_CLOSED = 5;
}
enum OrganizationType {
ORGANIZATION_TYPE_UNSPECIFIED = 0;
ORGANIZATION_TYPE_FARM = 1;
ORGANIZATION_TYPE_COOPERATIVE = 2;
ORGANIZATION_TYPE_INSURER = 3;
ORGANIZATION_TYPE_PROFESSIONAL = 4;
}
message Membership {
string id = 1;
string user_id = 2;
// The Organization the membership gives access to: its own for a direct membership, the delegator for a delegated one.
string organization_id = 3;
string role_id = 4;
MembershipType type = 5;
// Set for a delegated membership.
string delegation_id = 6;
google.protobuf.Timestamp created_at = 7;
// Empty while active.
google.protobuf.Timestamp revoked_at = 8;
// Set for a role through a user group; id is then the stable id derived from the group membership and the group's
// role assignment, the same as in membership.granted.v1.
string user_group_id = 9;
}
enum MembershipType {
MEMBERSHIP_TYPE_UNSPECIFIED = 0;
MEMBERSHIP_TYPE_DIRECT = 1;
MEMBERSHIP_TYPE_DELEGATED = 2;
// A role given to a user group the user belongs to, in the group's Organization.
MEMBERSHIP_TYPE_DIRECT_GROUP_ROLE = 3;
// A role given to a user group through a delegation, in the delegator.
MEMBERSHIP_TYPE_DELEGATED_GROUP_ROLE = 4;
}
message Delegation {
string id = 1;
string delegator_id = 2;
string delegatee_id = 3;
string role_id = 4;
google.protobuf.Timestamp revoked_at = 5;
}
message OrganizationRole {
string id = 1;
string name = 2;
repeated string capabilities = 3;
int32 priority = 4;
// The stable key of a predefined role (req 6.6, 1028), such as owner or sync_client; empty for any other role.
// Systems find a predefined role by this key, never by its name.
string key = 5;
}
message RegisterServiceAccountRequest {
string organization_id = 1;
string description = 2;
// The Person responsible for it.
string owner_user_id = 3;
// ID of the OrganizationRoleAccessControl it holds; for a sync client the Sync Client role.
string role_id = 4;
// JSON Web Key of the public key the program signs its token requests with.
string public_key_jwk = 5;
}
message RegisterServiceAccountResponse {
// The User (ServiceAccount) created; the program authenticates as this id.
string user_id = 1;
string membership_id = 2;
}
message DisableServiceAccountRequest {
string organization_id = 1;
// The User (ServiceAccount) to disable, as RegisterServiceAccount returned it.
string user_id = 2;
DisableReason reason = 3;
// Who asked, for the audit event: a Person of the Organization or a staff member. Neither, when the calling system
// decided it alone; the audit event then names the system as its actor (req 70.14).
oneof requested_by {
string requested_by_user_id = 4;
// The MoonitoringUser, when staff retired the client.
string requested_by_staff_user_id = 5;
}
}
enum DisableReason {
DISABLE_REASON_UNSPECIFIED = 0;
// The client was retired by a person or by staff.
DISABLE_REASON_RETIRED = 1;
// The client was installed again and enrolled with a new credential.
DISABLE_REASON_REINSTALLED = 2;
}
message DisableServiceAccountResponse {
string user_id = 1;
google.protobuf.Timestamp disabled_at = 2;
}
5.4.2.1.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.