Teams
Teams let you share skills with a whole group at once, so a new teammate can run
the skills they need the moment they join instead of waiting for a one-off grant.
You create a team with a handle, add members through invitations, and then grant a
skill to @teamhandle. Every current and future member gains the access level you
set on the grant, and the verifiers the skill references travel with it.
How teams work
A team has three parts:
- A handle, chosen at creation and immutable afterward.
- An owner, the person who created it or last accepted an ownership transfer. The owner has implicit membership: they are not listed as a member internally but still appear in member listings.
- Zero or more members.
You need a claimed user handle before you can create a team. See Accounts and Billing for handle setup.
Creating a team
The handle must be 3 to 40 characters, lowercase alphanumeric with hyphens, and
unique across the platform. It cannot be changed after creation. If your own user
handle is still provisional, creation fails with handle_not_claimed; run
goodeye me claim-handle first.
- CLI:
goodeye teams create my-team - MCP tool:
create_team - REST:
POST /v1/teams
Listing teams
The filter accepts all (default), mine (teams you own), or member (teams
you belong to). Each result carries team_id, handle, owner_user_id, your
role, and timestamps.
- CLI:
goodeye teams list(--filter mine|member|all) - MCP tool:
list_teams - REST:
GET /v1/teams(?filter=mine|member)
Deleting a team
Only the owner can delete a team. Deleting frees the team handle, which becomes claimable again after a hold period (see Accounts and Billing).
- CLI:
goodeye teams delete my-team(--yesto skip the prompt) - MCP tool:
delete_team - REST:
DELETE /v1/teams/{team_id_or_handle}
Managing members
Listing members
The listing includes every member and the owner. Each row carries user_id,
handle, and role; your own email is shown, while other members' emails are
redacted and only their handle appears. Listing is visible to the owner and all
members; a non-member receives 404 so the team's existence stays masked.
- CLI:
goodeye teams members my-team - MCP tool:
list_team_members - REST:
GET /v1/teams/{team_id_or_handle}/members
Adding a member
Adding a member creates an invitation; the recipient must accept before they join
(see Invitations). Identify them by UUID, email, or handle (with
or without the @). The response is an invitation envelope: tell the recipient to
run goodeye invitations accept <invitation_id>.
- CLI:
goodeye teams add-member my-team alice@example.com(orgoodeye teams add-member my-team @alice) - MCP tool:
add_team_member - REST:
POST /v1/teams/{team_id_or_handle}/members
Removing a member
The owner can remove any member, and members can remove themselves. To remove the owner, transfer ownership first.
- CLI:
goodeye teams remove-member my-team @alice - MCP tool:
remove_team_member - REST:
DELETE /v1/teams/{team_id_or_handle}/members/{user_identifier}
Transferring team ownership
Ownership transfer uses the invitation pattern: the current owner proposes the transfer, and the recipient must accept before ownership changes hands. On accept, the previous owner automatically becomes a member of the team.
- CLI:
goodeye teams transfer-ownership my-team @newowner - MCP tool:
transfer_team_ownership - REST:
POST /v1/teams/{team_id_or_handle}/transfer-ownership
Granting a skill to a team
Once your team exists, share a skill with the entire team in one step. All
current and future members can fetch that skill at the granted role. The
grantee is the team's @handle, and the role is one of view, edit, or
admin.
- CLI:
goodeye skills grant my-skill @my-team view - MCP tool:
grant_skill - REST:
POST /v1/skills/{id_or_slug}/grants
The REST body names the grantee with grantee_email_or_at_team_handle:
{"grantee_email_or_at_team_handle": "@my-team", "role": "view"}
For the full description of roles, version floors, and cascaded verifier grants, see Skills.
Invitations
Every mutating team operation that affects another user goes through an invitation: both adding a member and transferring ownership return an invitation envelope that the recipient must explicitly accept. The recipient is also emailed when the invitation is created, with a link to accept it.
When an operation creates a pending invitation, the response carries an
invitation_id, a kind (team_membership or team_ownership), and an
expires_at:
{
"invitation_id": "550e8400-e29b-41d4-a716-446655440000",
"kind": "team_membership",
"expires_at": "2026-07-11T00:00:00+00:00"
}
Accepting or declining
Only the recipient can accept or decline. Accepting applies the underlying action immediately (membership is created, or ownership changes hands); declining changes nothing.
- CLI:
goodeye invitations accept <invitation_id>/goodeye invitations decline <invitation_id> - MCP tool:
accept_invitation/decline_invitation - REST:
POST /v1/invitations/{invitation_id}/accept/POST /v1/invitations/{invitation_id}/decline
Listing, cancelling, and expiry
List invitations with --filter (received default, sent, or all) and
--state (pending default, or all). Only the proposer can cancel a pending
invitation, which stops the recipient from accepting. Invitations expire
automatically after a set window; a later add-member or transfer-ownership
after expiry simply creates a fresh one.
- CLI:
goodeye invitations list/goodeye invitations cancel <invitation_id> - MCP tool:
list_invitations/cancel_invitation - REST:
GET /v1/invitations
See also
- Skills for grant roles, version floors, and revoking access.
- Accounts and Billing for handles, API keys, and usage.
- Templates for public sharing via published template versions.