Team Management
Invite team members, assign roles, and manage permissions within your workspace
Collaborate with your team by inviting members to your workspace. Each member is assigned a role that determines their permissions.
Roles
| Role | Value | Description |
|---|---|---|
Owner | 0 | Full access, billing, can transfer ownership |
Admin | 1 | Full access except billing and ownership transfer |
Member | 2 | Send emails, manage domains, view analytics |
Viewer | 3 | Read-only access to all resources |
Role Permissions
Member is not read-only and not excluded from key management: it can create and edit almost everything. Only deleting API keys, domains and webhooks, plus team/ownership administration, is Admin-only.
| Permission | Owner | Admin | Member | Viewer |
|---|---|---|---|---|
| Send emails | Yes | Yes | Yes | No |
| Manage domains | Yes | Yes | Yes | No |
| Manage subscribers | Yes | Yes | Yes | No |
| View analytics | Yes | Yes | Yes | Yes |
| Create/rotate API keys | Yes | Yes | Yes | No |
| Delete API keys / domains / webhooks | Yes | Yes | No | No |
| Create/edit webhooks | Yes | Yes | Yes | No |
| Edit settings (including tracking) | Yes | Yes | Yes | No |
| Invite members | Yes | Yes | No | No |
| Remove members | Yes | Yes | No | No |
| Update roles | Yes | Yes | No | No |
| Manage billing | Yes | No | No | No |
| Transfer ownership | Yes | No | No | No |
| Delete workspace | Yes | No | No | No |
Team Overview
Get the current team members and pending invitations:
GET https://api.mail.land/v1/account/teamcurl https://api.mail.land/v1/account/team \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."{
"members": [
{
"publicId": "mbr_a1b2c3d4e5",
"email": "[email protected]",
"fullName": "John Doe",
"role": "owner",
"status": "active",
"joinedAt": "2026-01-15T08:00:00Z",
"invitedByEmail": null,
"isCurrentUser": true
},
{
"publicId": "mbr_f6g7h8i9j0",
"email": "[email protected]",
"fullName": "Jane Smith",
"role": "admin",
"status": "active",
"joinedAt": "2026-01-20T10:00:00Z",
"invitedByEmail": "[email protected]",
"isCurrentUser": false
}
],
"pendingInvitations": [
{
"publicId": "inv_k1l2m3n4o5",
"email": "[email protected]",
"role": "member",
"status": "pending",
"createdAt": "2026-02-17T08:00:00Z",
"expiresAt": "2026-02-24T08:00:00Z",
"invitedByEmail": "[email protected]",
"invitedByName": "John Doe",
"isExpired": false
}
],
"totalMembers": 2,
"totalPendingInvitations": 1,
"maxMembers": 10
}There is no id, userId, firstName/lastName, roleName/statusName, invitedBy, memberCount or invitationCount in this response: use the field names above instead. maxMembers reflects the workspace's plan: 10 on Free and Pro, unlimited on Max. A seat is an active member (the owner included) or a pending, unexpired invitation; suspended members and expired, revoked or accepted invitations don't count. At the cap, inviting, accepting, reactivating or an SSO first login all return 400 PLAN_LIMIT_REACHED. Downgrading a plan never removes or suspends anyone: it only blocks new admissions while the workspace is over its new limit.
Member Statuses
| Status | Description |
|---|---|
active | Active team member |
suspended | Suspended by an admin |
Invitation Statuses
Returned as lowercase strings:
| Status | Description |
|---|---|
pending | Awaiting acceptance |
accepted | Invitation accepted |
expired | Invitation expired (7 days) |
revoked | Invitation revoked (not "cancelled") |
Invite a Team Member
POST https://api.mail.land/v1/account/team/invite{
"email": "[email protected]",
"role": 2
}| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address to invite |
role | int | Yes | Role to assign (1–3, cannot invite as Owner) |
curl -X POST https://api.mail.land/v1/account/team/invite \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "role": 2 }'The invited user receives an email with a link to accept the invitation. Invitations expire after 7 days.
Accept an Invitation
There is a single public route for accepting an invitation: it is not versioned (/v1 is not part of the path), and it is keyed by the invitation token, not an id:
POST https://api.mail.land/invitations/{token}/acceptFor an existing MailLand user, sign in as the invited email address first, then call this route with an empty or minimal body, no firstName/lastName/password needed. For a brand-new user, provide fullName and password in the same call to create the account and accept in one step.
{
"fullName": "New Member",
"password": "SecureP@ssw0rd!"
}curl -X POST https://api.mail.land/invitations/invitation-token-from-email/accept \
-H "Content-Type: application/json" \
-d '{"fullName": "New Member", "password": "SecureP@ssw0rd!"}'There is no POST /v1/account/team/invitations/{id}/accept, no separate /accept-public route, and no GET /v1/team/invitations/{id}?token=. Both "existing user" and "new user" acceptance go through this one public /invitations/{token}/accept endpoint (Controllers/Public/InvitationsController.cs).
Get Invitation Info
Retrieve invitation details before accepting (public, no auth required, keyed by token, not /v1):
GET https://api.mail.land/invitations/{token}curl "https://api.mail.land/invitations/invitation-token-from-email"{
"tenantName": "My Company",
"role": "member",
"invitedByName": "John Doe",
"expiresAt": "2026-02-24T08:00:00Z",
"isExpired": false,
"alreadyAccepted": false,
"existingUserEmail": "[email protected]"
}Manage Invitations
Cancel an Invitation
DELETE https://api.mail.land/v1/account/team/invitations/{id}curl -X DELETE https://api.mail.land/v1/account/team/invitations/{id} \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Returns 204 No Content on success.
Resend an Invitation
POST https://api.mail.land/v1/account/team/invitations/{id}/resendcurl -X POST https://api.mail.land/v1/account/team/invitations/{id}/resend \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Resending an invitation resets the 7-day expiry.
Update a Member's Role
PATCH https://api.mail.land/v1/account/team/members/{id}/role{
"role": 1
}curl -X PATCH https://api.mail.land/v1/account/team/members/{id}/role \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{ "role": 1 }'Only Owners and Admins can update roles. You cannot change your own role or promote someone to Owner (use ownership transfer instead).
Admins can only manage Member/Viewer roles: they cannot promote a member to Admin or change another Admin's role. Only the Owner can do that.
Suspend and Unsuspend
Temporarily suspend a member's access:
POST https://api.mail.land/v1/account/team/members/{id}/suspendA body { reason?, credentialDecision?, transferToMemberId? } controls what happens to that member's API keys while suspended. credentialDecision is only required when the member owns at least one live API key: omitting it in that case returns 400 CREDENTIAL_DECISION_REQUIRED, with the exposed keys listed in details. Otherwise the whole body may be omitted.
| Field | Values |
|---|---|
reason | offboarding, compromise, abuse, other |
credentialDecision | revoke, keep, transfer |
transferToMemberId | Required when credentialDecision is transfer |
curl -X POST https://api.mail.land/v1/account/team/members/{id}/suspend \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Restore a suspended member:
POST https://api.mail.land/v1/account/team/members/{id}/reactivatecurl -X POST https://api.mail.land/v1/account/team/members/{id}/reactivate \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Suspended members cannot sign in to the workspace; their API keys follow the credential decision made at suspension time: with keep, the member's keys keep working.
Remove a Member
DELETE https://api.mail.land/v1/account/team/members/{id}Removal accepts the same credential decision, as query parameters:
DELETE https://api.mail.land/v1/account/team/members/{id}?reason=offboarding&credentialDecision=revoke&transferToMemberId=mbr_f6g7h8i9j0As with suspend, credentialDecision is required when the member owns a live API key and the call otherwise returns 400 CREDENTIAL_DECISION_REQUIRED.
curl -X DELETE https://api.mail.land/v1/account/team/members/{id} \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Removes the member from the workspace. They lose access to all workspace resources immediately.
Transfer Ownership
Transfer workspace ownership to another member:
POST https://api.mail.land/v1/account/team/transfer-ownership{
"newOwnerMemberId": "mbr_f6g7h8i9j0"
}curl -X POST https://api.mail.land/v1/account/team/transfer-ownership \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{ "newOwnerMemberId": "mbr_f6g7h8i9j0" }'Ownership transfer is irreversible. The current owner is demoted to Admin, and the target member becomes the new Owner.
The target must be an active Admin (not a Member or Viewer).
Error Responses
| Status | Code | Description |
|---|---|---|
400 | VALIDATION_FAILED | Invalid email or role |
400 | INVALID_REQUEST | More than 50 pending invitations for this workspace |
400 | CREDENTIAL_DECISION_REQUIRED | Suspend/remove target owns live API keys and no credentialDecision was supplied |
400 | CREDENTIAL_TRANSFER_TARGET_INVALID | credentialDecision: transfer with a missing or invalid transferToMemberId |
403 | FORBIDDEN | Insufficient permissions for this action |
404 | RESOURCE_NOT_FOUND | Member or invitation not found |
400 | PLAN_LIMIT_REACHED | Workspace is at its plan's seat limit (invite, accept, reactivate, or SSO first login) |
409 | CONFLICT | User is already a member, or already has a pending invitation |
There is no MEMBER_ALREADY_EXISTS code: both "already a member" and "invitation already exists" return 409 CONFLICT.
POST /v1/account/team/leave (leave the workspace yourself) and GET /v1/account/team/me (your own membership record) also exist and are not documented elsewhere on this page.
Next Steps
- Workspaces: manage workspace settings
- Authentication: authentication and sessions
- Billing: subscription and payment management