> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asteragents.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk User Invitation

> Invite multiple users to your organization at once with smart handling for existing users

<Note>
  This endpoint requires organization admin privileges. Only users with the `org:admin` role can bulk invite users to their organization.
</Note>

<Info>
  **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
</Info>

### Authentication

<ParamField header="Authorization" type="string" required>
  Bearer token for authentication. Must be from a user with `org:admin` role.
</ParamField>

### Body

<ParamField body="invitations" type="array" required>
  Array of invitation objects (1-50 invitations per request)

  <Expandable title="Invitation Object Properties">
    <ParamField body="email" type="string" required>
      Email address of the user to invite
    </ParamField>

    <ParamField body="metadata" type="object" optional>
      Organization-scoped user metadata (role, department, etc.) that will be accessible after signup. Each organization maintains separate metadata.
    </ParamField>
  </Expandable>
</ParamField>

### Response

<ResponseField name="success" type="boolean">
  Whether all invitations were successfully created
</ResponseField>

<ResponseField name="total" type="number">
  Total number of invitations requested
</ResponseField>

<ResponseField name="successful" type="number">
  Number of invitations successfully created
</ResponseField>

<ResponseField name="failed" type="number">
  Number of invitations that failed to create
</ResponseField>

<ResponseField name="results" type="array">
  Array of successful invitation results

  <Expandable title="Result Object">
    <ResponseField name="email" type="string">
      Email address of the user
    </ResponseField>

    <ResponseField name="success" type="boolean">
      Whether this operation was successful
    </ResponseField>

    <ResponseField name="type" type="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
    </ResponseField>

    <ResponseField name="status" type="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
    </ResponseField>

    <ResponseField name="invitation_id" type="string" optional>
      Clerk invitation ID (only for type="invitation")
    </ResponseField>

    <ResponseField name="user_id" type="string" optional>
      Clerk user ID (for existing users)
    </ResponseField>

    <ResponseField name="membership_id" type="string" optional>
      Organization membership ID (for direct memberships)
    </ResponseField>

    <ResponseField name="expires_at" type="string" optional>
      ISO date string of when invitation expires (only for invitations)
    </ResponseField>

    <ResponseField name="role" type="string" optional>
      User's role in organization (for existing memberships)
    </ResponseField>

    <ResponseField name="metadata" type="object">
      The metadata associated with this user
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="errors" type="array" optional>
  Array of failed invitations (only present if there were failures)

  <Expandable title="Error Object">
    <ResponseField name="email" type="string">
      Email address that failed
    </ResponseField>

    <ResponseField name="success" type="boolean">
      Always false for error objects
    </ResponseField>

    <ResponseField name="error" type="string">
      Description of what went wrong
    </ResponseField>
  </Expandable>
</ResponseField>

### Examples

<CodeGroup>
  ```curl Simple Invitations theme={null}
  curl -X POST https://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 With Metadata theme={null}
  curl -X POST https://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"
          }
        }
      ]
    }'
  ```

  ```python theme={null}
  import requests

  url = "https://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")
  ```

  ```javascript theme={null}
  const response = await fetch('https://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`);
  ```

  ```json Success Response - Mixed User Types theme={null}
  {
    "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": {}
      }
    ]
  }
  ```

  ```json Partial Success Response theme={null}
  {
    "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"
      }
    ]
  }
  ```
</CodeGroup>

### Error Codes

<ResponseField name="400" type="object">
  Bad Request - Invalid request data or validation errors
</ResponseField>

<ResponseField name="401" type="object">
  Unauthorized - Invalid or missing authentication
</ResponseField>

<ResponseField name="403" type="object">
  Forbidden - User is not an admin in the organization
</ResponseField>

<ResponseField name="405" type="object">
  Method Not Allowed - Only POST requests are accepted
</ResponseField>

<ResponseField name="207" type="object">
  Multi-Status - Some invitations succeeded, others failed (partial success)
</ResponseField>

### 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)

**Organization Isolation**: Metadata is scoped to your organization. If a user joins multiple organizations, each org maintains separate metadata for that user.

Metadata becomes accessible to both frontend and backend after user signs up and can be updated later via the [Update User Metadata](/api-reference/endpoint/update-user-metadata) endpoint.

<Warning>
  For sensitive data that should only be server-accessible, set it after signup using Clerk webhooks.
</Warning>

### 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
