> You are reading the Docusign Developer Documentation. For clean Markdown of any page, append .md to the page URL.
>
> Documentation indexes by section:
> - APIs and SDKs: https://docusign.docs.buildwithfern.com/docs/llms.txt
> - Platform 101: https://docusign.docs.buildwithfern.com/platform/llms.txt
> - Extension Apps: https://docusign.docs.buildwithfern.com/extension-apps/llms.txt
> - Docusign CLI: https://docusign.docs.buildwithfern.com/docusign-cli/llms.txt
>
> Authentication (OAuth, JWT Grant, M2M): https://docusign.docs.buildwithfern.com/platform/auth
> Webhooks and Connect: https://docusign.docs.buildwithfern.com/platform/webhooks
>
> OpenAPI specifications: https://docusign.docs.buildwithfern.com/openapi.json
> MCP server: https://docusign.docs.buildwithfern.com/_mcp/server

# How to add users via bulk import

> Learn how to create and update large numbers of users within an organization account simultaneously.

This topic demonstrates how to bulk import users and add user data from an example CSV (comma-separated value) file into a Docusign Admin organization.

> **Note**
>
> Run this code example from our [Quickstart](/docs/esign-rest-api/quickstart) or launcher projects: [Bash](https://github.com/docusign/code-examples-bash), [C#](https://github.com/docusign/code-examples-csharp), [Java](https://github.com/docusign/code-examples-java), [Node.js](https://github.com/docusign/code-examples-node), [PHP](https://github.com/docusign/code-examples-php), [PowerShell](https://github.com/docusign/code-examples-powershell), [Python](https://github.com/docusign/code-examples-python), or [Ruby](https://github.com/docusign/code-examples-ruby).

## Required data

Running the code in this how-to requires this data:

| Data element       | Description                                                                                                                                                                                                                                                                                                                                                                 |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \{ACCOUNT_ID}      | A [GUID](http://guid.one/) value that identifies the account to which the user will be added. To determine your account IDs, open your [Organization](https://apps-d.docusign.com/admin/organization/) home page in Docusign Admin, then select the **Accounts** tile. The **Account ID** values for each account in your organization will be shown on the resulting page. |
| Admin access       | The caller must be at least one of the following:- A full organization administrator
- A delegated organization administratorSee [Organization Administrators](https://support.docusign.com/s/document-item?language=en_US\&rsc_301\&bundleId=rrf1583359212854\&topicId=god1583359152535.html&_LANG=enus) for details on Docusign Admin roles.                              |
| \{ORGANIZATION_ID} | A [GUID](http://guid.one/) value that identifies the the organization that contains the account to which the user will be added. Copy the value from the **Organization ID** field in the **Organization** section of the [Account Profile](https://admindemo.docusign.com/authenticate?goTo=accountProfile) page.                                                          |
| User data          | A CSV file containing the user data to add, typically generated through a [bulk export](/docs/admin-api/how-to/bulk-export-users) operation. The user data contained in this CSV will be imported into your organization.                                                                                                                                                   |

## Step 1. Obtain your OAuth token

To make the API calls shown in this how-to, you need a valid OAuth access token. Docusign supports access tokens for three different recommended OAuth grant types: Public Authorization Code Grant, Confidential Authorization Code Grant, and JSON Web Token (JWT) Grant. Choose the OAuth grant type appropriate for your scenario.

Docusign Admin OAuth access tokens are the same ones used for our [eSignature REST API](/docs/esign-rest-api/esign101/auth), although you must request one or more Docusign Admin scopes to call the Docusign Admin API. The endpoint calls made in this how-to require that you request the Docusign Admin-specific `user_write` scope when obtaining your access token.

> **Note**
>
> See [Docusign Admin Authentication](/docs/admin-api/admin101/auth) for details and the full list of Docusign Admin scopes.

![Three icons that represent the three methods a user can use to obtain an OAuth token: JSON Web Token (JWT), Public Authorization Code Grant (ACG), and Confidential Authorization Code grant](/_fern-files/_img/64f473773042ee439449a096bf7673292b1585b86a4ccc55f82ba0dda41f61a1.webp)

## Step 2. Construct your API headers

After you have a valid OAuth access token, you are ready to construct the API headers that you will pass to the API call. Construct your headers like this, but make sure to substitute your valid OAuth access token for `{ACCESS_TOKEN}`.

**`Bash`**

```bash title="Bash"
declare -a Headers=('--header' "Authorization: Bearer ${ACCESS_TOKEN}"
   '--header' "Accept: application/json"
   '--header' "Content-Type: application/json")
```

**`C#`**

```csharp title="C#"
var docusignClient = new DocuSignClient(basePath);
docusignClient.Configuration.DefaultHeader.Add("Authorization", "Bearer " + accessToken);
var accountsApi = new AccountsApi(docusignClient);
```

**`Java`**

```java title="Java"
ApiClient apiClient = new ApiClient(basePath);
apiClient.addDefaultHeader(
  HttpHeaders.AUTHORIZATION,
  BEARER_AUTHENTICATION + accessToken );
```

**`Node.js`**

```javascript title="Node.js"
const apiClient = new docusignAdmin.ApiClient();
apiClient.setBasePath(args.basePath);
apiClient.addDefaultHeader("Authorization", "Bearer " + args.accessToken);
```

**`PHP`**

```php title="PHP"
$config = new Configuration();
$config->setAccessToken($accessToken);
$config->setHost('https://api-d.docusign.net/management');
$config->addDefaultHeader('Authorization', 'Bearer ' . $accessToken);
$this->apiClient = new ApiClient($config);
```

**`PowerShell`**

```text title="PowerShell"
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.add("Authorization", "Bearer $accessToken")
$headers.add("Content-Type", "application/json")
$headers.add("Accept", "application/json")
```

**`Python`**

```python title="Python"
api_client = ApiClient(host=DS_CONFIG["admin_api_client_host"])
api_client.set_default_header(
  header_name="Authorization",
  header_value=f"Bearer {access_token}"
)
```

**`Ruby`**

```ruby title="Ruby"
configuration = DocuSign_Admin::Configuration.new
configuration.host = Rails.configuration.admin_host
api_client = DocuSign_Admin::ApiClient.new(configuration)
api_client.set_default_header('Authorization', "Bearer #{args[:access_token]}")
```

## Step 3. Create the bulk import request

To import the user data from a CSV file, call [UserImport:addBulkUserImport](/docs/admin-api/reference/bulkoperations/userimport/addbulkuserimport) to create a new bulk import add users request. When complete, the users whose data is contained in this CSV file will be added to the specified account in your organization.

Rather than specifying a file to use for the request, the CSV data is defined within the request body, as demonstrated in this how-to.

**Note:** When adding user data, the `AccountId`, `UserName`, `FirstName`, `LastName`, `UserEmail`, and `PermissionSet` fields are required. For updating user data, `APIUsername` is required to identify the user, rather than `UserName`/`FirstName`/`LastName`, and `PermissionSet` is not required.

**`Bash`**

```bash title="Bash"
request_data=$(mktemp /tmp/request-oa.XXXXXX)

printf \
    'AccountID,UserName,UserEmail,PermissionSet
\"'${API_ACCOUNT_ID}'\",FirstLast1,exampleuser1@example.com,DS Viewer,
\"'${API_ACCOUNT_ID}'\",FirstLast2,exampleuser2@example.com,DS Viewer
' >>$request_data

# Create a temporary file to store the response
response=$(mktemp /tmp/response-oa.XXXXXX)

curl --request POST ${base_path}/v2/organizations/${ORGANIZATION_ID}/imports/bulk_users/add \
    "${Headers[@]}" \
    --data-binary @${request_data} \
    --output ${response}

echo ''
echo 'Response:'
echo ''
cat $response
echo ''
```

**`C#`**

```csharp title="C#"
var bulkImportsApi = new BulkImportsApi(apiClient);

var csvFileData = File.ReadAllText(csvFilePath)
    .Replace("{account_id}", accountId);

var bytes = Encoding.UTF8.GetBytes(csvFileData);

var response = bulkImportsApi.CreateBulkImportAddUsersRequestWithHttpInfo(organizationId, bytes);
response.Headers.TryGetValue("X-RateLimit-Remaining", out string remaining);
response.Headers.TryGetValue("X-RateLimit-Reset", out string reset);

if (reset != null && remaining != null)
{
    DateTime resetDate = DateTimeOffset.FromUnixTimeSeconds(long.Parse(reset)).UtcDateTime;
    Console.WriteLine("API calls remaining: " + remaining);
    Console.WriteLine("Next Reset: " + resetDate);
}

return response.Data;
```

**`Java`**

```java title="Java"
BulkImportsApi bulkImportsApi = createBulkImportsApi(accessToken, this.session.getBasePath());

return BulkImportUserDataService.bulkImportUserData(bulkImportsApi, organizationId, accountId);
String csvUserData = String.format(BULK_IMPORT_TEXT, accountId, accountId);
byte[] csvDataInBytes = csvUserData.getBytes(StandardCharsets.UTF_8);

ApiResponse<OrganizationImportResponse> response = bulkImportsApi
        .createBulkImportSingleAccountAddUsersRequestWithHttpInfo(organizationId, accountId, csvDataInBytes);

Map<String, List<String>> headers = response.getHeaders();
List<String> remaining = headers.get("X-RateLimit-Remaining");
List<String> reset = headers.get("X-RateLimit-Reset");

if (remaining != null & reset != null) {
    Instant resetInstant = Instant.ofEpochSecond(Long.parseLong(reset.get(0)));
    System.out.println("API calls remaining: " + remaining);
    System.out.println("Next Reset: " + resetInstant);
}

return response.getData();
```

**`Node.js`**

```javascript title="Node.js"
const bulkImportsApi = new docusign.BulkImportsApi(apiClient);

// get the user data from the csv file
let userData = fs.readFileSync(args.csvFilePath).toString();

// replace all {account_id} occurrences with the actual account id value
userData = Buffer.from(userData.replace(/{account_id}/g, args.accountId));

var response = await bulkImportsApi.createBulkImportAddUsersRequest(args.organizationId, userData, (error, data, response) => {
    const headers = response?.headers;

    const remaining = headers?.['x-ratelimit-remaining'];
    const reset = headers?.['x-ratelimit-reset'];

    if (remaining && reset) {
      const resetInstant = new Date(Number(reset) * 1000);

      console.log(`API calls remaining: ${remaining}`);
      console.log(`Next Reset: ${resetInstant.toISOString()}`);
    }
});
return response.data;
```

**`PHP`**

```php title="PHP"
$bulkImport = $clientService->bulkImportsApi();
$organizationImportResponse = $bulkImport->createBulkImportAddUsersRequestWithHttpInfo(
    $organizationId,
    new SplFileObject($csvFile)
);

$remaining = $organizationImportResponse[2]['X-RateLimit-Remaining'] ?? null;
$reset = $organizationImportResponse[2]['X-RateLimit-Reset'] ?? null;

if ($remaining !== null && $reset !== null) {
    $resetInstant = (new DateTime())->setTimestamp((int)$reset);
    error_log("API calls remaining: $remaining");
    error_log("Next Reset: " . $resetInstant->format(\DateTime::ATOM));
}
```

**`PowerShell`**

```text title="PowerShell"
$body = @"
AccountID,AccountName,FirstName,LastName,UserEmail,eSignPermissionProfile,Group,Language,UserTitle,CompanyName,AddressLine1,AddressLine2,City,StateRegionProvince,PostalCode,Phone,LoginPolicy,AutoActivate
$accountId,Sample Account,John,Markson,user1email@example.com,Account Administrator,Everyone,en,Mr.,Some Division,123 4th St,Suite C1,Seattle,WA,8178,2065559999,fedAuthRequired,true
$accountId,Sample Account,Jill,Smith,user2email@example.com,Account Administrator,Everyone,en,Mr.,Some Division,123 4th St,Suite C1,Seattle,WA,8178,2065559999,fedAuthRequired,true
$accountId,Sample Account,James,Grayson,user3emailt@example.com,Account Administrator,Everyone,en,Mr.,Some Division,123 4th St,Suite C1,Seattle,WA,8178,2065559999,fedAuthRequired,true
"@

$uri1 = "${base_path}/v2/organizations/$organizationId/imports/bulk_users/add"
$result1 = Invoke-WebRequest -headers $headers1 -Uri $uri1 -body $body -Method POST
$result1.Content
$results = $result1 | ConvertFrom-Json
$importId = $results.id
```

**`Python`**

```python title="Python"
import_api = BulkImportsApi(api_client=api_client)

# Setting headers for creating bulk import request
header_name, header_value = "Content-Disposition", "filename=myfile.csv"
api_client.set_default_header(header_name, header_value)

# Returns the response from the create_bulk_import_add_users_request method
(response, status, headers) = import_api.create_bulk_import_add_users_request_with_http_info(
    organization_id,
    csv_file_path
)

remaining = headers.get("X-RateLimit-Remaining")
reset = headers.get("X-RateLimit-Reset")

if remaining is not None and reset is not None:
    reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
    print(f"API calls remaining: {remaining}")
    print(f"Next Reset: {reset_date}")
```

**`Ruby`**

```ruby title="Ruby"
csv_file_data = File.open(args[:csv_file_path]).read
csv_file_data = csv_file_data.gsub('{account_id}', args[:account_id])

@bulk_imports_api = DocuSign_Admin::BulkImportsApi.new(api_client)
results, _status, headers = @bulk_imports_api.create_bulk_import_add_users_request_with_http_info(args[:organization_id], csv_file_data)

remaining = headers['X-RateLimit-Remaining']
reset = headers['X-RateLimit-Reset']

if remaining && reset
  reset_date = Time.at(reset.to_i).utc
  puts "API calls remaining: #{remaining}"
  puts "Next Reset: #{reset_date}"
end
```

## Expected response

You should see a JSON response similar to this.

The important parts of the **Sample response** are highlighted in the table below.

**`Sample response`**

```json title="Sample response"
{
   id: "cd400bc0-xxxx-xxxx-xxxx-c79a7c93416f",
   type: "add_users",
   requestor: {
      name: "Susan Signer",
      id: "c316f86c-xxxx-xxxx-xxxx-6b39358381d2",
      type: "user",
      email: "test@email.com"
   },
   created: "2022-12-14T19:25:41.660Z",
   last_modified: "2022-12-14T19:25:41.660Z",
   status: "queued",
   has_csv_results: false
}
```

| Property | Description                                                                                                                                                                                                                                                                           |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`     | Identifies the request. You can use this value in a call to [UserImport:getBulkUserImportRequest](/docs/admin-api/reference/bulkoperations/userimport/getbulkuserimportrequest) to return the status, details, and metadata for the bulk import data, as shown in Step 4.             |
| `status` | The status of the request. This value will be `queued`, after creation. Possible values are:- `unknown`: Unknown status
- `queued`: Ready to be created
- `in_process`: The export is being created
- `completed`: The export was created
- `failed`: The export could not be created |

If you receive a **401 - Unauthorized** response, your access token is not valid for the endpoint. If you receive this error:

* Make sure that your access token has `user_read` and `user_write` scopes.
* Make sure that the calling user has admin rights.

Creating and provisioning the bulk import list can take some time, especially for large organizations spread out over several regions. You can check the status of your bulk import request by calling [UserImport:getBulkUserImportRequest](/docs/admin-api/reference/bulkoperations/userimport/getbulkuserimportrequest) as demonstrated in Step 4.

## Step 4. Check the request status

It can take some time for the response to a bulk user import request to be fully created, depending on the number of users that it contains and the regions it encompasses. You can call [UserImport:getBulkUserImportRequest](/docs/admin-api/reference/bulkoperations/userimport/getbulkuserimportrequest) to check the status of your pending request and, if complete, obtain a URL that you can call to download the CSV file containing the data to verify that the updates were made.

**Important:** Be sure to set the `importId` value in the path with the import ID of the bulk user export request that you retrieved in the previous step.

> **Note**
>
> View the source code for this how-to on GitHub:  [Bash](https://github.com/docusign/code-examples-bash/blob/master/examples/Admin/eg004AddUsersViaBulkImport.sh), [C#](https://github.com/docusign/code-examples-csharp/blob/master/launcher-csharp/Admin/Examples/ImportUser.cs), [Java](https://github.com/docusign/code-examples-java/blob/master/src/main/java/com/docusign/controller/admin/services/BulkImportUserDataService.java), [Node.js](https://github.com/docusign/code-examples-node/blob/master/lib/admin/examples/importUser.js), [PHP](https://github.com/docusign/code-examples-php/blob/master/src/Services/Examples/Admin/BulkImportUserDataService.php), [PowerShell](https://github.com/docusign/code-examples-powershell/blob/master/examples/Admin/eg004AddUsersViaBulkImport.ps1), [Python](https://github.com/docusign/code-examples-python/blob/master/app/admin/examples/eg004_add_users_via_bulk_import.py), or [Ruby](https://github.com/docusign/code-examples-ruby/blob/master/app/services/admin_api/eg004_import_user_service.rb).

**`Bash`**

```bash title="Bash"
curl --request GET ${base_path}/v2/organizations/${ORGANIZATION_ID}/imports/bulk_users/${importId} \
    "${Headers[@]}" \
    --output ${response}
```

**`C#`**

```csharp title="C#"
var apiClient = new DocuSignClient(basePath);
apiClient.Configuration.DefaultHeader.Add("Authorization", "Bearer " + accessToken);
var bulkImportsApi = new BulkImportsApi(apiClient);

var response = bulkImportsApi.GetBulkUserImportRequestWithHttpInfo(organizationId, importId);
response.Headers.TryGetValue("X-RateLimit-Remaining", out string remaining);
response.Headers.TryGetValue("X-RateLimit-Reset", out string reset);

if (reset != null && remaining != null)
{
    DateTime resetDate = DateTimeOffset.FromUnixTimeSeconds(long.Parse(reset)).UtcDateTime;
    Console.WriteLine("API calls remaining: " + remaining);
    Console.WriteLine("Next Reset: " + resetDate);
}

return response.Data;
```

**`Java`**

```java title="Java"
return CheckImportRequestStatusService.checkRequestStatus(
        bulkImportsApi,
        this.getOrganizationId(this.user.getAccessToken(), this.session.getBasePath()),
        UUID.fromString(this.session.getImportId()));
```

**`Node.js`**

```javascript title="Node.js"
var response = await bulkImportsApi.getBulkUserImportRequest(args.organizationId, args.importId, (error, data, response) => {
    const headers = response?.headers;

    const remaining = headers?.['x-ratelimit-remaining'];
    const reset = headers?.['x-ratelimit-reset'];

    if (remaining && reset) {
      const resetInstant = new Date(Number(reset) * 1000);

      console.log(`API calls remaining: ${remaining}`);
      console.log(`Next Reset: ${resetInstant.toISOString()}`);
    }
});
return response.data;
```

**`PHP`**

```php title="PHP"
$importId = $_SESSION['import_id'];
$response = $bulkImport->getBulkUserImportRequestWithHttpInfo(
    $this->clientService->getOrgAdminId($this->args),
    $importId
);

$remaining = $response[2]['X-RateLimit-Remaining'] ?? null;
$reset =  $response[2]['X-RateLimit-Reset'] ?? null;

if ($remaining !== null && $reset !== null) {
    $resetInstant = (new DateTime())->setTimestamp((int)$reset);
    error_log("API calls remaining: $remaining");
    error_log("Next Reset: " . $resetInstant->format(\DateTime::ATOM));
}
return $response[0];
```

**`PowerShell`**

```text title="PowerShell"
$uri2 = "${base_path}/v2/organizations/$organizationId/imports/bulk_users/$importId"
$result2 = Invoke-WebRequest -headers $headers2 -Uri $uri2 -Method GET
$result2.Content
```

**`Python`**

```python title="Python"
(import_results, status, headers) = import_api.get_bulk_user_import_request_with_http_info(
    organization_id,
    session['import_data_id']
)

remaining = headers.get("X-RateLimit-Remaining")
reset = headers.get("X-RateLimit-Reset")

if remaining is not None and reset is not None:
    reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
    print(f"API calls remaining: {remaining}")
    print(f"Next Reset: {reset_date}")
```

**`Ruby`**

```ruby title="Ruby"
bulk_imports_api = DocuSign_Admin::BulkImportsApi.new(@api_client)
bulk_imports_api.get_bulk_user_import_request(args[:organization_id], import_id)
```

## Expected response

You should see a JSON response similar to this.

**`Sample response`**

```json title="Sample response"
{
   id: "cd400bc0-xxxx-xxxx-xxxx-c79a7c93416f",
   type: "add_users",
   requestor: {
      name: "Susan Signer",
      id: "c316f86c-xxxx-xxxx-xxxx-6b39358381d2",
      type: "user",
      email: "test@email.com"
   },
   created: "2022-12-14T19:25:41.660Z",
   last_modified: "2022-12-14T19:27:02.169Z",
   status: "completed",
   user_count: 2,
   processed_user_count: 2,
   added_user_count: 0,
   updated_user_count: 2,
   closed_user_count: 0,
   no_action_required_user_count: 0,
   error_count: 0,
   warning_count: 0,
   has_csv_results: true,
   results_uri: "/organizations/d4327916-xxxx-xxxx-xxxx-b74833ce8087/imports/bulk_users/cd400bc0-xxxx-xxxx-xxxx-c79a7c93416f/results_csv"
}
```

## Next steps

* Run this code example from our [Quickstart](/docs/esign-rest-api/quickstart) or launcher projects:    [Bash](https://github.com/docusign/code-examples-bash), [C#](https://github.com/docusign/code-examples-csharp), [Java](https://github.com/docusign/code-examples-java), [Node.js](https://github.com/docusign/code-examples-node), [PHP](https://github.com/docusign/code-examples-php), [PowerShell](https://github.com/docusign/code-examples-powershell), [Python](https://github.com/docusign/code-examples-python), or [Ruby](https://github.com/docusign/code-examples-ruby).
* Learn [How to create a new active eSignature user](/docs/admin-api/how-to/create-active-user) for details on how to create a new active organization user.
* See [How to bulk export user data](/docs/admin-api/how-to/bulk-export-users) for details on how to use bulk export to create or update large numbers of user accounts within an organization or to verify your account users and their permissions.
* See [Docusign Admin concepts](/docs/admin-api/admin101/concepts) for more information on Docusign Admin capabilities and features.
* See [Go-Live](/docs/admin-api/go-live) to find the base path for Docusign Admin API calls in the production environment.