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
ownerdoes not grant ownership silently — it creates a pending in-portal invitation the invitee must accept (returned withpending:true).
Creating an agent
Agents are not invited — they are minted. The same invite_member tool does it with kind: "agent" (the portal’s Add member modal is one surface with a human/agent toggle, and so is this), with the same authority (admin on the org):
{ "name": "invite_member",
"arguments": { "kind": "agent", "organizationId": "…", "name": "Miami site agent",
"role": "developer", "projectId": "…" } }
- The response carries the agent’s
principalId, its username (issued on the reserved agent domain unless you passagentEmailon a domain the org has proven it controls) and thetdm_token, shown once. roledefaults todeveloperand can never exceed your own. Credential action masks are deprecated (AUTHZ-40/42) — restrict via grants, pillar levels, andprincipal_limits, not a frozen credential mask.projectId/serviceIdfence the credential: the agent can then reach only that project’s or service’s resources — the way to give one agent exactly one site. A fenced caller can only mint within its own fence.- Passing an
agentEmailthat already belongs to an agent adds that existing agent to the org with the role instead of minting a new one (addedExisting: true, no new token; its home org keeps managing its credentials).set_member_roleon the agent’sprincipalIddoes the same for an agent already known to you. - Change what the agent may do later, without a new token, with
update_agent_credential; inspect withlist_agent_credentials.
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_memberscannot 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.