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

# Bulk upload contacts

> Batch import contacts from CSV or Excel (.xlsx/.xls) spreadsheets with automatic deduplication

Upload a CSV or Excel spreadsheet to import or update large cohorts of contacts simultaneously. If a contact with an identical phone number already exists within your organization, their name and tags are updated in-place; otherwise, a new contact record is created.

***

### Authentication

This endpoint requires Bearer token authentication.

```http theme={null}
Authorization: Bearer <token>
```

| Header          | Type     | Required | Description                                     | Format           |
| :-------------- | :------- | :------- | :---------------------------------------------- | :--------------- |
| `Authorization` | `string` | **Yes**  | Scoped organization API key or Bearer JWT token | `Bearer <token>` |

***

### File Requirements

* **Supported Formats**: `.csv`, `.xlsx`, `.xls`
* **Required Columns** (case-insensitive headers):
  * `phone_number`: Destination telephone number (E.164 format recommended).
  * `name`: Contact full name or display name.
* **Optional Columns**:
  * `tags`: Tag strings separated by commas or semicolons (e.g. `vip; enterprise; q3_leads`).

#### Example CSV Structure

```csv theme={null}
phone_number,name,tags
+14155550101,Alice Smith,vip;retail
+14155550102,Bob Jones,lead;inbound
+14155550103,Carol White,enterprise
```

***

### Input parameters

<ParamField header="Authorization" type="string" required>
  Bearer token for authentication. Format: `Bearer <token>`.
</ParamField>

<ParamField body="file" type="file" required>
  The multipart form file upload (`.csv`, `.xlsx`, or `.xls`).
</ParamField>

***

### Response Fields

<ResponseField name="message" type="string">
  Human-readable summary of upload completion.
</ResponseField>

<ResponseField name="total_processed" type="number">
  Total number of row records parsed from the file.
</ResponseField>

<ResponseField name="created" type="number">
  Number of brand new contacts provisioned.
</ResponseField>

<ResponseField name="updated" type="number">
  Number of existing contacts whose profile or tags were updated.
</ResponseField>

<ResponseField name="failed" type="number">
  Number of invalid rows that failed to import.
</ResponseField>

<ResponseField name="errors" type="string[]">
  Itemized error descriptions for rows that failed validation (e.g. `["Row 4: Missing phone_number or name"]`).
</ResponseField>

***

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.sayvy.ai/api/v1/contacts/bulk-upload" \
    -H "Authorization: Bearer <token>" \
    -F "file=@/path/to/contacts.csv"
  ```

  ```python Python theme={null}
  import requests

  url = "https://api.sayvy.ai/api/v1/contacts/bulk-upload"

  headers = {
      "Authorization": "Bearer <token>"
  }

  with open("contacts.csv", "rb") as f:
      files = {"file": ("contacts.csv", f, "text/csv")}
      response = requests.post(url, headers=headers, files=files)

  print(response.status_code)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const formData = new FormData();
  formData.append("file", fileInputElement.files[0]);

  const response = await fetch("https://api.sayvy.ai/api/v1/contacts/bulk-upload", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <token>"
    },
    body: formData
  });

  const data = await response.json();
  console.log(data);
  ```

  ```java Java theme={null}
  // Using Java 11+ HttpClient with multipart body publisher
  HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://api.sayvy.ai/api/v1/contacts/bulk-upload"))
      .header("Authorization", "Bearer <token>")
      .POST(ofMimeMultipartData(Map.of("file", Paths.get("contacts.csv")), boundary))
      .build();

  HttpResponse<String> response =
      HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
  System.out.println(response.body());
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "message": "Bulk upload completed. Processed 250 contacts.",
    "total_processed": 250,
    "created": 210,
    "updated": 38,
    "failed": 2,
    "errors": [
      "Row 42: Missing phone_number or name",
      "Row 118: Invalid phone number format"
    ]
  }
  ```

  ```json 400 Bad Request (Unsupported File) theme={null}
  {
    "detail": "Unsupported file format. Please upload CSV or Excel file."
  }
  ```

  ```json 400 Bad Request (Missing Columns) theme={null}
  {
    "detail": "Missing required columns: phone_number, name"
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "detail": "Unauthorized"
  }
  ```
</ResponseExample>

***

<div
  style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "rgba(255, 255, 255, 0.03)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "16px",
padding: "10px 18px",
marginTop: "40px",
gap: "16px",
flexWrap: "wrap"
}}
>
  <a
    href="/api-reference/contacts/delete-contact"
    style={{
display: "inline-flex",
alignItems: "center",
gap: "6px",
color: "#94A3B8",
textDecoration: "none",
fontSize: "14px",
fontWeight: "500",
padding: "4px 8px"
}}
  >
    <span style={{ fontSize: "16px" }}>‹</span> Previous
  </a>

  <div
    style={{
display: "flex",
alignItems: "center",
gap: "16px",
backgroundColor: "rgba(255, 255, 255, 0.04)",
border: "1px solid rgba(255, 255, 255, 0.06)",
borderRadius: "12px",
padding: "8px 16px",
marginLeft: "auto"
}}
  >
    <div style={{ textAlign: "right" }}>
      <div style={{ fontSize: "13px", fontWeight: "700", color: "#F8FAFC" }}>Contacts Overview</div>

      <div style={{ fontSize: "11px", color: "#94A3B8", maxWidth: "260px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
        Back to Contacts documentation hub
      </div>
    </div>

    <div style={{ width: "1px", height: "24px", backgroundColor: "rgba(255, 255, 255, 0.1)" }} />

    <a
      href="/api-reference/contacts/overview"
      style={{
  display: "inline-flex",
  alignItems: "center",
  gap: "6px",
  color: "#94A3B8",
  textDecoration: "none",
  fontSize: "14px",
  fontWeight: "500"
}}
    >
      Overview <span style={{ fontSize: "16px" }}>›</span>
    </a>
  </div>
</div>
