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

# Bulk Invite Users

> Invite several people to your organization in one request.

Invites a list of people in one call. Every entry behaves exactly like a single [Invite User](/platform-api/invitations/invite) request: an address with no outstanding invitation is invited, and one that already has an invitation gets it resent — with a role change if the role differs. This is the common integration case for onboarding a team from a list.

<Warning>
  **All-or-nothing.** One bad entry undoes the whole batch: nothing is written and nobody is emailed. Applying some entries and reporting the rest would make a retry ambiguous, and re-sending the batch would mail everyone who already succeeded a second time.
</Warning>

## Request body

| Parameter     | Type  | Required | Description                                                             |
| ------------- | ----- | -------- | ----------------------------------------------------------------------- |
| `invitations` | array | Yes      | The people to invite. Each entry has the same shape as a single invite. |

Each entry in `invitations` accepts:

| Field                   | Type   | Required | Description                                                                            |
| ----------------------- | ------ | -------- | -------------------------------------------------------------------------------------- |
| `email`                 | string | Yes      | The address to invite. Normalized before storing, so casing never creates a duplicate. |
| `role`                  | string | No       | One of `owner`, `admin`, or `member`. Defaults to `member`.                            |
| `first_name`            | string | No       | Shown in the member list before the person accepts.                                    |
| `last_name`             | string | No       | Shown in the member list before the person accepts.                                    |
| `functional_area`       | string | No       | The invitee's functional area.                                                         |
| `other_functional_area` | string | No       | Free-text functional area, when none of the predefined options fit.                    |

<Note>
  Each entry behaves exactly like a single [Invite User](/platform-api/invitations/invite) request, so the same fields and validation rules apply.
</Note>

## Response

Returns `200 OK` with a summary of what the batch did, split by what happened to each address.

```json theme={null}
{
  "message": "Successfully invited 2 user(s).",
  "invited": ["analyst@example.com", "lead@example.com"],
  "resent": []
}
```

| Field     | Description                                              |
| --------- | -------------------------------------------------------- |
| `message` | Human-readable summary of what the bulk invite did.      |
| `invited` | Addresses that had no outstanding invitation and now do. |
| `resent`  | Addresses whose outstanding invitation was resent.       |

`invited` and `resent` together always account for every entry you sent.

## Example

```bash theme={null}
curl --request POST \
  --url https://api.nekt.ai/api/v1/invitations/bulk-invite/ \
  --header "x-api-key: YOUR_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "invitations": [
      {"email": "analyst@example.com"},
      {"email": "scientist@example.com"},
      {"email": "lead@example.com", "role": "admin"}
    ]
  }'
```

## Python example: onboard a team from a list

```python theme={null}
import os

import requests

BASE_URL = "https://api.nekt.ai"
headers = {
    "x-api-key": os.environ["NEKT_API_KEY"],
    "Content-Type": "application/json",
}

emails = ["analyst@example.com", "scientist@example.com", "lead@example.com"]

response = requests.post(
    f"{BASE_URL}/api/v1/invitations/bulk-invite/",
    headers=headers,
    # `role` omitted, so everyone comes in as a member.
    json={"invitations": [{"email": email} for email in emails]},
)
response.raise_for_status()

body = response.json()
print(f"invited: {body['invited']}")
print(f"resent:  {body['resent']}")
```

## Resending

Sending the same batch again is safe and is how you chase people who have not accepted. Nobody is duplicated: addresses that already have an invitation come back under `resent` with a fresh expiry, and their existing groups and grants are untouched.

```json theme={null}
{
  "message": "Successfully invited 1 user(s) and resent 2 invitation(s).",
  "invited": ["newcomer@example.com"],
  "resent": ["analyst@example.com", "scientist@example.com"]
}
```

<Tip>
  If an entry names a different role than the outstanding invitation has, the resend moves the person to it — on both the invitation and their pending membership. Moving an existing `admin` or `owner` requires an owner.
</Tip>

## Authentication

This endpoint requires an [API key](/platform-api/introduction#create-an-api-key) in the `x-api-key` header.

Only an **owner or an admin** can invite, and nobody can invite somebody to a role above their own. For an API key, that ceiling is read from the role of the user who created the key.

## Errors

Every one of these rejects the **whole** batch.

| Status | When                                                                     |
| ------ | ------------------------------------------------------------------------ |
| `400`  | An address already belongs to an active member of the organization.      |
| `400`  | The same address appears twice in one payload.                           |
| `400`  | `invitations` is empty or missing, or an entry has no `email`.           |
| `403`  | The caller is a member, or an entry names a role above the caller's own. |

<Tip>
  Because one bad entry rejects everything, a long list built from an external source is worth filtering first — drop addresses that are already members, and de-duplicate. A rejected batch tells you what was wrong, but it writes nothing.
</Tip>

## Related

* [Invite User](/platform-api/invitations/invite) — invite or resend for a single person.
* [Invitation object](/platform-api/invitations/invitation) — the shape of each entry.


## OpenAPI

````yaml POST /api/v1/invitations/bulk-invite/
openapi: 3.0.3
info:
  title: Nekt API
  version: v1
  description: Nekt API Documentation
  contact:
    email: support@nekt.ai
servers:
  - url: https://api.nekt.ai
security: []
paths:
  /api/v1/invitations/bulk-invite/:
    post:
      tags:
        - v1
      description: >-
        Invite several people at once.


        Each entry behaves exactly like a single `POST /api/v1/invitations/`: an
        address with no

        outstanding invitation is invited, and one that already has an
        invitation gets it re-sent,

        with a role change if the role differs. Only `email` is required; `role`
        defaults to

        `member`.


        ALL-OR-NOTHING, by way of `ATOMIC_REQUESTS` and DRF's `set_rollback()`:
        one entry that

        fails -- an address that already belongs to a member, a role above the
        caller's own --

        undoes the whole batch. The alternative, applying some entries and
        reporting the rest as

        errors, makes a retry ambiguous: the caller cannot tell a
        duplicate-invite from a first

        attempt, and re-sending the batch would mail everyone who did succeed a
        second time.


        Emails are sent on commit rather than inline, so a batch that rolls back
        sends none. That

        is the one half of this a transaction cannot take back.
      operationId: v1_invitations_bulk_invite_create
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkInvitationInput'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/BulkInvitationInput'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/BulkInvitationInput'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkInvitationResponse'
          description: ''
      security:
        - ApiKeyAuth: []
components:
  schemas:
    BulkInvitationInput:
      type: object
      properties:
        invitations:
          type: array
          items:
            $ref: '#/components/schemas/Invitation'
          description: The people to invite.
      required:
        - invitations
    BulkInvitationResponse:
      type: object
      description: What a bulk invite did, split by what happened to each address.
      properties:
        message:
          type: string
          description: Human-readable summary of what the bulk invite did.
        invited:
          type: array
          items:
            type: string
            format: email
          description: Addresses that had no outstanding invitation and now do.
        resent:
          type: array
          items:
            type: string
            format: email
          description: Addresses whose outstanding invitation was re-sent.
      required:
        - invited
        - message
        - resent
    Invitation:
      type: object
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
        first_name:
          type: string
          maxLength: 150
        last_name:
          type: string
          maxLength: 150
        email:
          type: string
          format: email
          title: Email address
          maxLength: 254
        role:
          allOf:
            - $ref: '#/components/schemas/RoleEnum'
          default: member
          description: >-
            Defaults to `member`. You cannot invite somebody to a role above
            your own, and on the Free plan only owners can be invited.


            * `owner` - Owner

            * `admin` - Admin

            * `member` - Member
        functional_area:
          nullable: true
          oneOf:
            - $ref: '#/components/schemas/FunctionalAreaEnum'
            - $ref: '#/components/schemas/BlankEnum'
            - $ref: '#/components/schemas/NullEnum'
        other_functional_area:
          type: string
          nullable: true
          maxLength: 128
        sent_at:
          type: string
          format: date-time
          readOnly: true
        expires_at:
          type: string
          format: date-time
          readOnly: true
        accepted_at:
          type: string
          format: date-time
          readOnly: true
          nullable: true
        revoked_at:
          type: string
          format: date-time
          readOnly: true
          nullable: true
        created_at:
          type: string
          format: date-time
          readOnly: true
        created_by:
          allOf:
            - $ref: '#/components/schemas/OrganizationUser'
          readOnly: true
      required:
        - accepted_at
        - created_at
        - created_by
        - email
        - expires_at
        - id
        - revoked_at
        - sent_at
    RoleEnum:
      enum:
        - owner
        - admin
        - member
      type: string
      description: |-
        * `owner` - Owner
        * `admin` - Admin
        * `member` - Member
    FunctionalAreaEnum:
      enum:
        - data_analytics
        - sales
        - customer_support
        - operations
        - marketing
        - finance
        - product_development
        - growth
        - executive_leadership
        - other
      type: string
      description: |-
        * `data_analytics` - Data & Analytics
        * `sales` - Sales
        * `customer_support` - Customer Support
        * `operations` - Operations
        * `marketing` - Marketing
        * `finance` - Finance
        * `product_development` - Product Development
        * `growth` - Growth
        * `executive_leadership` - Executive Leadership
        * `other` - Other
    BlankEnum:
      enum:
        - ''
    NullEnum:
      enum:
        - null
    OrganizationUser:
      type: object
      properties:
        id:
          type: integer
          readOnly: true
        first_name:
          type: string
          readOnly: true
        last_name:
          type: string
          readOnly: true
        email:
          type: string
          format: email
          readOnly: true
          title: Email address
        picture:
          type: string
          format: uri
          readOnly: true
          nullable: true
        role:
          type: string
          readOnly: true
        functional_area:
          readOnly: true
          nullable: true
          oneOf:
            - $ref: '#/components/schemas/FunctionalAreaEnum'
            - $ref: '#/components/schemas/NullEnum'
        other_functional_area:
          type: string
          readOnly: true
          nullable: true
        date_joined:
          type: string
          format: date-time
          readOnly: true
        last_login:
          type: string
          format: date-time
          readOnly: true
          nullable: true
        is_active:
          type: boolean
          readOnly: true
          default: false
        is_pending:
          type: boolean
          readOnly: true
          default: false
      required:
        - date_joined
        - email
        - first_name
        - functional_area
        - id
        - is_active
        - is_pending
        - last_login
        - last_name
        - other_functional_area
        - picture
        - role
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: 'API Key authentication. Format: ''x-api-key: api_key'''

````