Team Members API
The Team Members API lets operators invite collaborators, assign roles, and control which service connections each member can access. Team members receive their own API key (sk_live_*) that resolves to the parent operator's organization, scoped to their permissions.
All management endpoints (invite, list, update, revoke) require admin-level access. The auth middleware checks the caller's role via requireRole(request, 'admin'). Non-admin callers receive 403 Forbidden.
Roles
Team members are assigned one of three roles:
readonly
View-only access. Can authenticate and retrieve credentials for services in theirallowed_connections list. Cannot modify any resources.
standard
Can use their API key to authenticate agents, retrieve credentials, connect services, invoke skills, and perform most operations. Cannot manage other team members. This is the default role.
admin
Full access to all team management operations. Can invite and revoke other members, update roles, and manage service connections. Cannot regenerate the operator's master API key.
Tier Limits
The number of team members you can invite depends on your subscription tier. Attempting to invite beyond your limit returns a 403 error.
- Free — 1 team member
- Developer ($9.99/mo) — 5 team members
- Studio ($99/mo) — 25 team members
- Enterprise — Unlimited team members
Invite a Team Member
Send an invitation to a new team member. The invitation stays in invited status until the member accepts it. Upon acceptance, they receive their own API key.
curl -X POST https://api.getstack.run/v1/team/members \
-H "Authorization: Bearer sk_live_master_key" \
-H "Content-Type: application/json" \
-d '{
"email": "developer@example.com",
"name": "Jane Developer",
"role": "standard",
"allowed_connections": ["slack", "github"]
}'InviteMemberInput Fields
- email (string, required) — the invitee's email address, must be a valid email
- name (string, required) — display name for the team member, 1-100 chars
- role (enum, optional) — "readonly" | "standard" | "admin", defaults to "standard"
- allowed_connections (string[], optional) — list of service provider keys the member can access. Omit to allow all connections.
Returns 201 with the created member object.
{
"id": "mem_abc123",
"operator_id": "op_xyz",
"email": "developer@example.com",
"name": "Jane Developer",
"role": "standard",
"status": "invited",
"allowed_connections": ["slack", "github"],
"created_at": "2026-04-15T10:00:00Z"
}List Team Members
Retrieve all team members for the current operator, including their status and role. Requires admin access.
curl https://api.getstack.run/v1/team/members \
-H "Authorization: Bearer sk_live_master_key"Returns an array of member objects. The response is a bare array, not wrapped in an envelope.
[
{
"id": "mem_abc123",
"email": "developer@example.com",
"name": "Jane Developer",
"role": "standard",
"status": "active",
"allowed_connections": ["slack", "github"],
"created_at": "2026-04-15T10:00:00Z"
},
{
"id": "mem_def456",
"email": "lead@example.com",
"name": "Team Lead",
"role": "admin",
"status": "invited",
"allowed_connections": null,
"created_at": "2026-04-14T08:00:00Z"
}
]Update a Team Member
Modify a team member's role or allowed connections. Changes take effect immediately. Requires admin access.
curl -X PATCH https://api.getstack.run/v1/team/members/mem_abc123 \
-H "Authorization: Bearer sk_live_master_key" \
-H "Content-Type: application/json" \
-d '{
"role": "admin",
"allowed_connections": ["slack", "github", "linear", "stripe"]
}'UpdateMemberInput Fields
- role (enum, optional) — "readonly" | "standard" | "admin"
- allowed_connections (string[], optional) — updated connection list. Pass null to grant access to all connections.
Returns the updated member object.
Setting allowed_connections to null grants the member access to all current and future service connections. Use explicit lists for principle-of-least-privilege access control.
Revoke a Team Member
Revoke a team member's access. Their API key is immediately invalidated and any subsequent requests using it will return 401 Unauthorized. Requires admin access.
curl -X DELETE https://api.getstack.run/v1/team/members/mem_abc123 \
-H "Authorization: Bearer sk_live_master_key"{
"success": true
}Revocation is permanent. To re-add a previously revoked member, send a new invitation to their email address. They will receive a new member ID and API key.
Accept an Invitation
Accept a pending team member invitation. This endpoint is unauthenticated — the member ID in the URL serves as the authentication token. Upon acceptance, the member receives their API key. The API key is shown only once and cannot be retrieved again.
curl -X POST https://api.getstack.run/v1/team/members/mem_abc123/accept{
"member_id": "mem_abc123",
"api_key": "sk_live_mem_abc123_secret_key",
"mcp_command": "claude mcp add stack --transport http https://mcp.getstack.run/mcp --header \"Authorization: Bearer sk_live_mem_abc123_secret_key\""
}Response Fields
- member_id — the member ID
- api_key — the member's API key (shown once, store it securely)
- mcp_command — a ready-to-use command for connecting Claude Code to STACK with this member key
The api_key is returned exactly once during acceptance. Store it securely. If lost, the operator must revoke and re-invite the member.
Member Authentication Flow
Member API keys follow the same format as operator keys (sk_live_*) and are used identically in the Authorization: Bearer header. The auth middleware resolves member keys to the parent operator's organization automatically.
# Member authenticating — resolves to parent operator's org
curl https://api.getstack.run/v1/credentials/slack \
-H "Authorization: Bearer sk_live_mem_abc123_secret_key"How Resolution Works
- Auth middleware first checks if the key matches a team member
- If matched, it loads the parent operator context and applies the member's allowed_connections filter
- If not a member key, it falls back to operator key lookup
- Credential retrieval respects allowed_connections — 403 for connections outside the member's scope
- Audit logs record both the member ID and the parent operator ID for full traceability
Connection Tracking
When a team member adds a service connection, the connected_by field on the connection record is set to their member ID. This provides an audit trail showing which team member established each connection.
{
"id": "conn_abc",
"provider": "slack",
"status": "active",
"connected_by": "mem_abc123",
"verified_at": "2026-04-15T10:05:00Z"
}Member Statuses
- invited — invitation sent, waiting for the member to accept
- active — member has accepted and has a valid API key
- revoked — access has been permanently revoked