Managing members and roles

An organization’s members are its principals — both humans and agents. These four tools mirror the portal’s Organization → Members panel. All read tools need the read action and viewer role; all mutations need the manage action and admin (or higher) role.

Roles

Roles are ordered viewer < developer < admin < owner. A tool that mutates state states the role it needs (e.g. DNS edits need developer; deletes and member changes need admin; billing payment methods and org-scoped limits need owner). Every org must always have at least one owner.

Listing members

{ "name": "list_members", "arguments": { "organizationId": "…" } }

Returns each member’s principalId, userId, kind (human/agent), role, email, displayName, whether a human still has setup pending, and when they joined.

Inviting a human

{ "name": "invite_member",
  "arguments": { "organizationId": "…", "email": "teammate@example.com",
                 "role": "developer", "displayName": "Sam" } }
  • A brand-new email creates the account and emails a setup link (also returned, so you can relay it).
  • Inviting an existing verified account as owner does not grant ownership silently — it creates a pending in-portal invitation the invitee must accept (returned with pending:true).

To add an agent to an org, its human admin adds it in the portal by the agent’s username (from the agent’s whoami), or use set_member_role on an already-invited principal.

Agent usernames and the domains you own

An agent’s username is an email-shaped handle — it is the agent’s own identity, never a person’s. Leave it blank when creating an agent and the platform issues one on its reserved agent domain.

If you supply one, it must be on a domain your organization has proven it controls. A claim is not proof; the platform looks for a fact it observed:

Proof How you get it
Registered through Tandem purchase_domain or transfer_domain
DNS served here the domain’s nameservers actually point at Tandem
Verified for email add_email_domain then verify_email_domain goes fully green
A verified custom hostname any hostname on the domain passed its live DNS check

Owning a domain covers its subdomains, so a claim on example.com also permits bot@mail.example.com. Anything else is refused with domain_not_controlled — this stops an organization minting a directory identity on a namespace belonging to someone else. Renaming an existing agent’s handle is checked the same way.

Giving an agent a real mailbox

When the handle sits on a domain you control, the portal offers to create an actual mailbox at that address so the agent can send and receive mail. It is off by default and never takes over a mailbox that already exists — share that one instead. If the domain is not yet switched on for email, accepting the offer onboards it first; mail starts flowing once the domain’s DNS records verify.

Operators: the check is enforced by default and can be put in observe-only mode with AGENT_EMAIL_REQUIRE_DOMAIN_CONTROL=false.

Changing a role

{ "name": "set_member_role",
  "arguments": { "organizationId": "…", "principalId": "…", "role": "admin" } }

Identify the member by userId (humans) or principalId (humans or agents). The tool refuses to demote the only owner — assign another owner first.

This is also how you promote a guest (see below), including one who has no membership row yet: the write is an upsert, so it creates the membership rather than failing. It does not touch the shares the guest already holds.

Guests

{ "name": "list_org_guests", "arguments": { "organizationId": "…" } }

A guest is a principal present in the org holding no role-derived capability — everything it can do came from an explicit share. Two populations, both returned:

  • members whose role is email. That role expands to an empty capability set; it exists so a share invitee can sign in and reach what was shared with them, and nothing else.
  • principals holding an active share in the org with no membership row at all. list_members cannot see these — it reads memberships — so this is the only way to enumerate them.

Each row carries hasMembership, presentSince, and a count of the shares held here; use list_principal_access for the shares themselves. A grantCount of 0 is a membership that confers nothing and reaches nothing — a remove_member candidate.

Needs admin role (stricter than list_members, because a share count is access-review data). Promote with set_member_role.

Removing a member

{ "name": "remove_member",
  "arguments": { "organizationId": "…", "principalId": "…" } }

Identify by userId or principalId. Refuses to remove the only owner. Unlike the portal, this does not delete a now-orphaned human account — that stays a deliberate admin action.

Related

To cap what a member (human or agent) is allowed to do — beyond their role — attach a policy with set_limits. See Limits and policies.