Send invitations
Sends email invitations to join an organization. Accepts a batch of 1–20 invitations, each with an email and optional role_slugs.
If no role_slugs are provided for an entry, it defaults to ["member"]. Each entry must contain exactly one system role (member, billing, admin, or owner). Custom roles (org-* prefix) are only allowed alongside "member".
Every entry must name between 1 and 10 team_ids. The invitee is joined to those teams the first time they log in, which is what decides the teams, members and domains they can see, so an invitation naming no team would offer access to nothing. Every named team must be live, belong to this organization, and be one the inviter can see themselves; teams outside the inviter's own scope are rejected as unknown.
A custom role that has been disabled cannot be invited with. A role is usually disabled because a plan change withdrew the capacity for it; its definition and permissions survive, so re-enabling it makes it invitable again. An entry naming one rejects the whole batch, so no invitation goes out granting less than was asked for.
Each invitation is sent independently; partial failures are reported per-entry in the response array. Every invitation record carries attribution for who triggered the send.
Permissions
| Key type | Accepted | Permission required | Notes |
|---|---|---|---|
| Org API key | yes | member:invite |
Key must be scoped to the org in the URL path. Privilege escalation guards are skipped — the admin granted member:invite at key-mint time, so the key may invite at any role level. WorkOS receives no inviter user ID; the local record stores the key ID for audit. |
| Personal API key | yes | member:invite |
Caller must have an active membership in the target org; pass X-Org-ID header matching the URL path. Privilege escalation guards apply to the caller's role level. |
Error Codes
| Code | HTTP | Description |
|---|---|---|
| authorize.unauthenticated | 401 | No valid auth credentials |
| authorize.forbidden | 403 | Missing member:invite permission or wrong org |
| mfa.step_up_required | 403 | Step-up authentication required (non-member role targets, session only) |
| invite.ip_rate_limited | 429 | Too many requests from this IP |
| invite.org_rate_limited | 429 | Organization invitation limit exceeded |
| invite.invalid_org_id | 400 | Org ID is malformed or has wrong prefix |
| invite.org_not_found | 404 | No organization with this ID |
| invite.unauthorized | 401 | Authentication required |
| invite.org_mismatch | 403 | Not authorized for this organization |
| invite.user_lookup_failed | 500 | Could not resolve calling user |
| invite.member_lookup_failed | 500 | Could not verify caller membership |
| invite.decode_failed | 400 | Malformed JSON body |
| invite.empty_batch | 400 | No invitations provided |
| invite.batch_too_large | 400 | More than 20 invitations in batch |
| invite.duplicate_email | 400 | Same email appears more than once |
| invite.invalid_email | 400 | Email is missing, invalid, or too long |
| invite.self_invite | 400 | Cannot invite yourself |
| invite.invalid_role | 400 | Unknown role slug |
| invite.no_system_role | 400 | No system role provided |
| invite.multiple_system_roles | 400 | More than one system role |
| invite.custom_roles_not_allowed | 400 | Custom roles only with member |
| invite.insufficient_role | 403 | Caller's role level too low or caller doesn't hold assigned custom role |
| invite.role_disabled | 422 | One or more named custom roles are disabled and cannot be invited with |
| invite.already_member | 409 | Invitee is already a member of this organization |
| invite.already_pending | 409 | A pending invitation already exists for this email |
| invite.role_resolve_failed | 400 | Could not resolve role slugs |
| invite.invalid_team_id | 400 | Team ID is malformed or has wrong prefix |
| invite.teams_required | 400 | No team named for an entry |
| invite.too_many_teams | 400 | More than 10 teams named for one entry |
| invite.team_not_found | 422 | Team does not exist in this organization, or is outside the inviter's scope |
| invite.send_failed | 500 | Invitation delivery failed |
Path Parameters
- Type: stringidrequired
Organization ID (e.g. org_01h455vb4pex5vsknk084sn02q)
Body
Invitation entries
- Type:Properties: 3
Responses
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
- application/json
curl 'https://api.nametrust.com/orgs/{id}/invitations' \
--request POST \
--header 'Content-Type: application/json' \
--data '[
{
"email": "jane@acme.com",
"role_slugs": [
"member"
],
"team_ids": [
"team_01h455vb4pex5vsknk084sn02q"
]
}
]'
[
{
"object": "invite_result",
"email": "jane@acme.com",
"success": true,
"invitation": {
"object": "invitation",
"id": "inv_01h455vb4pex5vsknk084sn02q",
"email": "jane@acme.com",
"state": "pending",
"role_slugs": [
"member"
],
"team_ids": [
"team_01h455vb4pex5vsknk084sn02q"
],
"expires_at": "2025-07-01T00:00:00Z",
"created_at": "2025-06-01T12:00:00Z"
},
"error": ""
}
]