Skip to content

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:

  • changeMemberRole gives 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 needs access.members.manage, already in the catalog.
  • listCooperativeMemberFarms shows a cooperative the membership data its farms share without a delegation. Its capability access.cooperative_members.read is 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 how access holds the number of animals and the last data of farm is 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.