# Add multiple members to team

POST https://app.launchdarkly.com/api/v2/teams/{teamKey}/members
Content-Type: multipart/form-data

Add multiple members to an existing team by uploading a CSV file of member email addresses. Your CSV file must include email addresses in the first column. You can include data in additional columns, but LaunchDarkly ignores all data outside the first column. Headers are optional. To learn more, read [Manage team members](https://launchdarkly.com/docs/home/account/manage-teams#manage-team-members).

**Members are only added on a `201` response.** A `207` indicates the CSV file contains a combination of valid and invalid entries. A `207` results in no members being added to the team.

On a `207` response, if an entry contains bad input, the `message` field contains the row number as well as the reason for the error. The `message` field is omitted if the entry is valid.

Example `207` response:
```json
{
  "items": [
    {
      "status": "success",
      "value": "new-team-member@acme.com"
    },
    {
      "message": "Line 2: empty row",
      "status": "error",
      "value": ""
    },
    {
      "message": "Line 3: email already exists in the specified team",
      "status": "error",
      "value": "existing-team-member@acme.com"
    },
    {
      "message": "Line 4: invalid email formatting",
      "status": "error",
      "value": "invalid email format"
    }
  ]
}
```

Message | Resolution
--- | ---
Empty row | This line is blank. Add an email address and try again.
Duplicate entry | This email address appears in the file twice. Remove the email from the file and try again.
Email already exists in the specified team | This member is already on your team. Remove the email from the file and try again.
Invalid formatting | This email address is not formatted correctly. Fix the formatting and try again.
Email does not belong to a LaunchDarkly member | The email address doesn't belong to a LaunchDarkly account member. Invite them to LaunchDarkly, then re-add them to the team.

On a `400` response, the `message` field may contain errors specific to this endpoint.

Example `400` response:
```json
{
  "code": "invalid_request",
  "message": "Unable to process file"
}
```

Message | Resolution
--- | ---
Unable to process file | LaunchDarkly could not process the file for an unspecified reason. Review your file for errors and try again.
File exceeds 25mb | Break up your file into multiple files of less than 25mbs each.
All emails have invalid formatting | None of the email addresses in the file are in the correct format. Fix the formatting and try again.
All emails belong to existing team members | All listed members are already on this team. Populate the file with member emails that do not belong to the team and try again.
File is empty | The CSV file does not contain any email addresses. Populate the file and try again.
No emails belong to members of your LaunchDarkly organization | None of the email addresses belong to members of your LaunchDarkly account. Invite these members to LaunchDarkly, then re-add them to the team.


Reference: https://launchdarkly.com/docs/api/teams/post-team-members

## OpenAPI Specification

```yaml
openapi: 3.1.0
info:
  title: LaunchDarkly REST API
  version: 1.0.0
paths:
  /api/v2/teams/{teamKey}/members:
    post:
      operationId: post-team-members
      summary: Add multiple members to team
      description: >
        Add multiple members to an existing team by uploading a CSV file of
        member email addresses. Your CSV file must include email addresses in
        the first column. You can include data in additional columns, but
        LaunchDarkly ignores all data outside the first column. Headers are
        optional. To learn more, read [Manage team
        members](https://launchdarkly.com/docs/home/account/manage-teams#manage-team-members).


        **Members are only added on a `201` response.** A `207` indicates the
        CSV file contains a combination of valid and invalid entries. A `207`
        results in no members being added to the team.


        On a `207` response, if an entry contains bad input, the `message` field
        contains the row number as well as the reason for the error. The
        `message` field is omitted if the entry is valid.


        Example `207` response:

        ```json

        {
          "items": [
            {
              "status": "success",
              "value": "new-team-member@acme.com"
            },
            {
              "message": "Line 2: empty row",
              "status": "error",
              "value": ""
            },
            {
              "message": "Line 3: email already exists in the specified team",
              "status": "error",
              "value": "existing-team-member@acme.com"
            },
            {
              "message": "Line 4: invalid email formatting",
              "status": "error",
              "value": "invalid email format"
            }
          ]
        }

        ```


        Message | Resolution

        --- | ---

        Empty row | This line is blank. Add an email address and try again.

        Duplicate entry | This email address appears in the file twice. Remove
        the email from the file and try again.

        Email already exists in the specified team | This member is already on
        your team. Remove the email from the file and try again.

        Invalid formatting | This email address is not formatted correctly. Fix
        the formatting and try again.

        Email does not belong to a LaunchDarkly member | The email address
        doesn't belong to a LaunchDarkly account member. Invite them to
        LaunchDarkly, then re-add them to the team.


        On a `400` response, the `message` field may contain errors specific to
        this endpoint.


        Example `400` response:

        ```json

        {
          "code": "invalid_request",
          "message": "Unable to process file"
        }

        ```


        Message | Resolution

        --- | ---

        Unable to process file | LaunchDarkly could not process the file for an
        unspecified reason. Review your file for errors and try again.

        File exceeds 25mb | Break up your file into multiple files of less than
        25mbs each.

        All emails have invalid formatting | None of the email addresses in the
        file are in the correct format. Fix the formatting and try again.

        All emails belong to existing team members | All listed members are
        already on this team. Populate the file with member emails that do not
        belong to the team and try again.

        File is empty | The CSV file does not contain any email addresses.
        Populate the file and try again.

        No emails belong to members of your LaunchDarkly organization | None of
        the email addresses belong to members of your LaunchDarkly account.
        Invite these members to LaunchDarkly, then re-add them to the team.
      tags:
        - subpackage_teams
      parameters:
        - name: teamKey
          in: path
          description: The team key
          required: true
          schema:
            type: string
            format: string
        - name: Authorization
          in: header
          required: true
          schema:
            type: string
      responses:
        '201':
          description: Team member imports response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamImportsRep'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvalidRequestErrorRep'
        '401':
          description: Invalid access token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedErrorRep'
        '405':
          description: Method not allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MethodNotAllowedErrorRep'
        '429':
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitedErrorRep'
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: CSV file containing email addresses
servers:
  - url: https://app.launchdarkly.com
  - url: https://app.launchdarkly.us
components:
  schemas:
    MemberImportItem:
      type: object
      properties:
        message:
          type: string
          description: >-
            An error message, including CSV line number, if the
            <code>status</code> is <code>error</code>
        status:
          type: string
          description: >-
            Whether this member can be successfully imported
            (<code>success</code>) or not (<code>error</code>). Even if the
            status is <code>success</code>, members are only added to a team on
            a <code>201</code> response.
        value:
          type: string
          description: >-
            The email address for the member requested to be added to this team.
            May be blank or an error, such as 'invalid email format', if the
            email address cannot be found or parsed.
      required:
        - status
        - value
      title: MemberImportItem
    TeamImportsRep:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/MemberImportItem'
          description: >-
            An array of details about the members requested to be added to this
            team
      title: TeamImportsRep
    InvalidRequestErrorRep:
      type: object
      properties:
        code:
          type: string
          description: Specific error code encountered
        message:
          type: string
          description: Description of the error
      required:
        - code
        - message
      title: InvalidRequestErrorRep
    UnauthorizedErrorRep:
      type: object
      properties:
        code:
          type: string
          description: Specific error code encountered
        message:
          type: string
          description: Description of the error
      required:
        - code
        - message
      title: UnauthorizedErrorRep
    MethodNotAllowedErrorRep:
      type: object
      properties:
        code:
          type: string
          description: Specific error code encountered
        message:
          type: string
          description: Description of the error
      required:
        - code
        - message
      title: MethodNotAllowedErrorRep
    RateLimitedErrorRep:
      type: object
      properties:
        code:
          type: string
          description: Specific error code encountered
        message:
          type: string
          description: Description of the error
      required:
        - code
        - message
      title: RateLimitedErrorRep
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: Authorization

```

## SDK Code Examples

```python
import requests

url = "https://app.launchdarkly.com/api/v2/teams/teamKey/members"

files = { "file": "open('<file1>', 'rb')" }
headers = {"Authorization": "<apiKey>"}

response = requests.post(url, files=files, headers=headers)

print(response.json())
```

```javascript
const url = 'https://app.launchdarkly.com/api/v2/teams/teamKey/members';
const form = new FormData();
form.append('file', '<file1>');

const options = {method: 'POST', headers: {Authorization: '<apiKey>'}};

options.body = form;

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://app.launchdarkly.com/api/v2/teams/teamKey/members"

	payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "<apiKey>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://app.launchdarkly.com/api/v2/teams/teamKey/members")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = '<apiKey>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://app.launchdarkly.com/api/v2/teams/teamKey/members")
  .header("Authorization", "<apiKey>")
  .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://app.launchdarkly.com/api/v2/teams/teamKey/members', [
  'multipart' => [
    [
        'name' => 'file',
        'filename' => '<file1>',
        'contents' => null
    ]
  ]
  'headers' => [
    'Authorization' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://app.launchdarkly.com/api/v2/teams/teamKey/members");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "<apiKey>");
request.AddParameter("undefined", "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "<apiKey>"]
let parameters = [
  [
    "name": "file",
    "fileName": "<file1>"
  ]
]

let boundary = "---011000010111000001101001"

var body = ""
var error: NSError? = nil
for param in parameters {
  let paramName = param["name"]!
  body += "--\(boundary)\r\n"
  body += "Content-Disposition:form-data; name=\"\(paramName)\""
  if let filename = param["fileName"] {
    let contentType = param["content-type"]!
    let fileContent = String(contentsOfFile: filename, encoding: String.Encoding.utf8)
    if (error != nil) {
      print(error as Any)
    }
    body += "; filename=\"\(filename)\"\r\n"
    body += "Content-Type: \(contentType)\r\n\r\n"
    body += fileContent
  } else if let paramValue = param["value"] {
    body += "\r\n\r\n\(paramValue)"
  }
}

let request = NSMutableURLRequest(url: NSURL(string: "https://app.launchdarkly.com/api/v2/teams/teamKey/members")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

```python
import requests

url = "https://app.launchdarkly.com/api/v2/teams/teamKey/members"

files = { "file": "open('<file1>', 'rb')" }
headers = {"Authorization": "<apiKey>"}

response = requests.post(url, files=files, headers=headers)

print(response.json())
```

```javascript
const url = 'https://app.launchdarkly.com/api/v2/teams/teamKey/members';
const form = new FormData();
form.append('file', '<file1>');

const options = {method: 'POST', headers: {Authorization: '<apiKey>'}};

options.body = form;

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://app.launchdarkly.com/api/v2/teams/teamKey/members"

	payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "<apiKey>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://app.launchdarkly.com/api/v2/teams/teamKey/members")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = '<apiKey>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://app.launchdarkly.com/api/v2/teams/teamKey/members")
  .header("Authorization", "<apiKey>")
  .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://app.launchdarkly.com/api/v2/teams/teamKey/members', [
  'multipart' => [
    [
        'name' => 'file',
        'filename' => '<file1>',
        'contents' => null
    ]
  ]
  'headers' => [
    'Authorization' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://app.launchdarkly.com/api/v2/teams/teamKey/members");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "<apiKey>");
request.AddParameter("undefined", "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001--\r\n", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "<apiKey>"]
let parameters = [
  [
    "name": "file",
    "fileName": "<file1>"
  ]
]

let boundary = "---011000010111000001101001"

var body = ""
var error: NSError? = nil
for param in parameters {
  let paramName = param["name"]!
  body += "--\(boundary)\r\n"
  body += "Content-Disposition:form-data; name=\"\(paramName)\""
  if let filename = param["fileName"] {
    let contentType = param["content-type"]!
    let fileContent = String(contentsOfFile: filename, encoding: String.Encoding.utf8)
    if (error != nil) {
      print(error as Any)
    }
    body += "; filename=\"\(filename)\"\r\n"
    body += "Content-Type: \(contentType)\r\n\r\n"
    body += fileContent
  } else if let paramValue = param["value"] {
    body += "\r\n\r\n\(paramValue)"
  }
}

let request = NSMutableURLRequest(url: NSURL(string: "https://app.launchdarkly.com/api/v2/teams/teamKey/members")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```