Bulk User Invitation
curl --request POST \
--url https://www.asteragents.com/api/admin/bulkInvite \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"invitations": [
{
"email": "<string>",
"metadata": {}
}
]
}
'import requests
url = "https://www.asteragents.com/api/admin/bulkInvite"
payload = { "invitations": [
{
"email": "<string>",
"metadata": {}
}
] }
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({invitations: [{email: '<string>', metadata: {}}]})
};
fetch('https://www.asteragents.com/api/admin/bulkInvite', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.asteragents.com/api/admin/bulkInvite",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'invitations' => [
[
'email' => '<string>',
'metadata' => [
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.asteragents.com/api/admin/bulkInvite"
payload := strings.NewReader("{\n \"invitations\": [\n {\n \"email\": \"<string>\",\n \"metadata\": {}\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.asteragents.com/api/admin/bulkInvite")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"invitations\": [\n {\n \"email\": \"<string>\",\n \"metadata\": {}\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.asteragents.com/api/admin/bulkInvite")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"invitations\": [\n {\n \"email\": \"<string>\",\n \"metadata\": {}\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"207": {},
"400": {},
"401": {},
"403": {},
"405": {},
"success": true,
"total": 123,
"successful": 123,
"failed": 123,
"results": [
{
"email": "<string>",
"success": true,
"type": "<string>",
"status": "<string>",
"invitation_id": "<string>",
"user_id": "<string>",
"membership_id": "<string>",
"expires_at": "<string>",
"role": "<string>",
"metadata": {}
}
],
"errors": [
{
"email": "<string>",
"success": true,
"error": "<string>"
}
]
}Legacy Endpoints (Deprecated)
Bulk User Invitation
Invite multiple users to your organization at once with smart handling for existing users
POST
/
admin
/
bulkInvite
Bulk User Invitation
curl --request POST \
--url https://www.asteragents.com/api/admin/bulkInvite \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"invitations": [
{
"email": "<string>",
"metadata": {}
}
]
}
'import requests
url = "https://www.asteragents.com/api/admin/bulkInvite"
payload = { "invitations": [
{
"email": "<string>",
"metadata": {}
}
] }
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({invitations: [{email: '<string>', metadata: {}}]})
};
fetch('https://www.asteragents.com/api/admin/bulkInvite', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.asteragents.com/api/admin/bulkInvite",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'invitations' => [
[
'email' => '<string>',
'metadata' => [
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.asteragents.com/api/admin/bulkInvite"
payload := strings.NewReader("{\n \"invitations\": [\n {\n \"email\": \"<string>\",\n \"metadata\": {}\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.asteragents.com/api/admin/bulkInvite")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"invitations\": [\n {\n \"email\": \"<string>\",\n \"metadata\": {}\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.asteragents.com/api/admin/bulkInvite")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"invitations\": [\n {\n \"email\": \"<string>\",\n \"metadata\": {}\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"207": {},
"400": {},
"401": {},
"403": {},
"405": {},
"success": true,
"total": 123,
"successful": 123,
"failed": 123,
"results": [
{
"email": "<string>",
"success": true,
"type": "<string>",
"status": "<string>",
"invitation_id": "<string>",
"user_id": "<string>",
"membership_id": "<string>",
"expires_at": "<string>",
"role": "<string>",
"metadata": {}
}
],
"errors": [
{
"email": "<string>",
"success": true,
"error": "<string>"
}
]
}This endpoint requires organization admin privileges. Only users with the
org:admin role can bulk invite users to their organization.Smart User Handling: This endpoint automatically detects existing users and handles them appropriately:
- New users: Sends email invitations
- Existing users: Adds them directly as organization members
- Already members: Reports their current status without errors
Authentication
string
required
Bearer token for authentication. Must be from a user with
org:admin role.Body
array
required
Response
boolean
Whether all invitations were successfully created
number
Total number of invitations requested
number
Number of invitations successfully created
number
Number of invitations that failed to create
array
Array of successful invitation results
Show Result Object
Show Result Object
string
Email address of the user
boolean
Whether this operation was successful
string
Type of operation performed:
"invitation": Email invitation sent to new user"direct_membership": Existing user added directly as member"existing_membership": User was already a member
string
Status of the user in organization:
"pending": Invitation sent, awaiting acceptance"active": User is now an active member"already_member": User was already a member
string
Clerk invitation ID (only for type=“invitation”)
string
Clerk user ID (for existing users)
string
Organization membership ID (for direct memberships)
string
ISO date string of when invitation expires (only for invitations)
string
User’s role in organization (for existing memberships)
object
The metadata associated with this user
array
Examples
curl -X POST https://www.asteragents.com/api/admin/bulkInvite \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"invitations": [
{
"email": "john@company.com"
},
{
"email": "sarah@company.com"
}
]
}'
curl -X POST https://www.asteragents.com/api/admin/bulkInvite \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"invitations": [
{
"email": "manager@company.com",
"metadata": {
"role": "manager",
"department": "engineering",
"team": "backend"
}
},
{
"email": "developer@company.com",
"metadata": {
"role": "developer",
"department": "engineering",
"level": "senior"
}
}
]
}'
import requests
url = "https://www.asteragents.com/api/admin/bulkInvite"
headers = {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
}
data = {
"invitations": [
{
"email": "user@company.com",
"metadata": {
"role": "developer",
"department": "engineering"
}
}
]
}
response = requests.post(url, headers=headers, json=data)
result = response.json()
print(f"Invited {result['successful']} users successfully")
const response = await fetch('https://www.asteragents.com/api/admin/bulkInvite', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
invitations: [
{
email: 'user@company.com',
metadata: {
role: 'developer',
department: 'engineering'
}
}
]
})
});
const result = await response.json();
console.log(`Invited ${result.successful} users successfully`);
{
"success": true,
"total": 3,
"successful": 3,
"failed": 0,
"results": [
{
"email": "newuser@company.com",
"success": true,
"type": "invitation",
"status": "pending",
"invitation_id": "inv_12345",
"expires_at": "2024-02-01T12:00:00.000Z",
"metadata": {}
},
{
"email": "existinguser@company.com",
"success": true,
"type": "direct_membership",
"status": "active",
"user_id": "user_abc123",
"membership_id": "mem_xyz789",
"metadata": { "role": "developer" }
},
{
"email": "currentmember@company.com",
"success": true,
"type": "existing_membership",
"status": "already_member",
"user_id": "user_def456",
"membership_id": "mem_uvw012",
"role": "org:member",
"metadata": {}
}
]
}
{
"success": false,
"total": 3,
"successful": 2,
"failed": 1,
"results": [
{
"email": "john@company.com",
"success": true,
"invitation_id": "inv_12345",
"status": "pending",
"expires_at": "2024-02-01T12:00:00.000Z",
"metadata": {}
},
{
"email": "sarah@company.com",
"success": true,
"invitation_id": "inv_67890",
"status": "pending",
"expires_at": "2024-02-01T12:00:00.000Z",
"metadata": { "role": "developer" }
}
],
"errors": [
{
"email": "invalid-email",
"success": false,
"error": "Invalid email address"
}
]
}
Error Codes
object
Bad Request - Invalid request data or validation errors
object
Unauthorized - Invalid or missing authentication
object
Forbidden - User is not an admin in the organization
object
Method Not Allowed - Only POST requests are accepted
object
Multi-Status - Some invitations succeeded, others failed (partial success)
Features
Smart User Detection: Automatically handles different user scenarios without requiring you to know their status beforehand.- New Users: Automatically sends email invitations with 30-day expiration
- Existing Users: Adds them directly as organization members (no email needed)
- Current Members: Gracefully reports their existing status without errors
- Re-invitations: Previously removed users can be seamlessly re-added
- Mixed Batches: Process new and existing users in the same request
- Zero Errors: No “user already exists” failures - all scenarios handled intelligently
Metadata
You can set organization-scoped user metadata during invitation:- User roles (
role: "manager") - Department info (
department: "engineering") - Team assignments (
team: "backend") - Custom properties (any key-value pairs)
For sensitive data that should only be server-accessible, set it after signup using Clerk webhooks.
Limits
- Batch Size: 1-50 invitations per request
- Email Validation: All email addresses must be valid
- Rate Limiting: Subject to Clerk’s API rate limits
- Expiration: Invitations expire after 30 days