# Customers

> 💡 **Bulk upsert customers for a tenant with a single API call**

***

## 🚀 TL;DR - Quick Reference

**Endpoint**: `POST /customers/{tenantId}`

**Use When**:

* New customer signup (no purchase)
* Updating customer preferences
* Syncing customer profiles from your system
* GDPR opt-in/opt-out updates

**Required**: At least one of `CustomerId` or `Email`\
**Auth**: `x-replenit-auth-key` header\
**Returns**: Count of upserted customers

**Quickest Example**:

```bash
POST /customers/{YOUR_TENANT_ID}
[{"CustomerId": "C-001", "Email": "user@example.com"}]
```

[Jump to Full API Reference ↓](#endpoint)

***

## 🔗 Endpoint

```http
POST /customers/{tenantId}
```

### 📋 Parameters

| Parameter  | Type   | Required | Description                        |
| ---------- | ------ | -------- | ---------------------------------- |
| `tenantId` | `GUID` | ✅ Yes    | The tenant identifier (must exist) |

### 📨 Request

* **Body**: JSON array of `UpsertCustomerDto` objects
* **Content-Type**: `application/json`

### ✅ Success Response

* **Status**: `200 OK`
* **Body**: Response envelope with `data.count`

### ❌ Error Responses

| Status                      | Description                          |
| --------------------------- | ------------------------------------ |
| `400 Bad Request`           | Invalid or missing tenant identifier |
| `404 Not Found`             | Tenant does not exist in our system  |
| `500 Internal Server Error` | Unexpected server errors             |

***

## 🎯 Identifier Behavior

Understanding how customer identifiers work is crucial for successful data synchronization.

### How Customer Matching Works

When you send customer data, the system needs to determine whether you're creating a new customer or updating an existing one. This is done using an **identifier field**.

Your tenant configuration specifies which field takes priority:

| Priority Setting | Identifier Used                | When to Use                                     |
| ---------------- | ------------------------------ | ----------------------------------------------- |
| `email`          | Customer's email address       | Best for B2C where emails are unique and stable |
| `customerid`     | Customer's ID from your system | Best for B2B or when emails can change          |

### What This Means for You

**If priority is `email`**:

* The system matches customers by their email address
* If an email already exists, that customer record is updated
* If the email is new, a new customer is created
* `CustomerId` can be provided but won't be used for matching

**If priority is `customerid`**:

* The system matches customers by your internal customer ID
* If the ID already exists, that customer record is updated
* If the ID is new, a new customer is created
* `Email` can be provided but won't be used for matching

### Best Practice

**Always provide both** `CustomerId` and `Email` when possible. This ensures:

* Proper matching based on your tenant's configuration
* Complete customer profiles
* Better campaign personalization
* Reliable customer identification even if one identifier changes

**Example**:

```json
{
  "CustomerId": "USER-12345",  // Your internal ID
  "Email": "customer@example.com",  // Their email
  "Name": "Jane",
  "Surname": "Doe"
}
```

> 📝 **Note**: To view or change your identifier priority setting, navigate to your tenant configuration in the Replenit Dashboard.

***

## 📄 Request Schema

### UpsertCustomerDto Fields

All fields are optional unless specified otherwise. The API uses intelligent defaults for missing values.

| Field           | Type     | Max Length | Required       | Description                                                                                                |
| --------------- | -------- | ---------- | -------------- | ---------------------------------------------------------------------------------------------------------- |
| `CustomerId`    | `string` | 100        | ⚠️ Conditional | Your unique identifier for this customer. Either this or `Email` is required depending on tenant settings. |
| `Email`         | `string` | 255        | ⚠️ Conditional | Customer's email address. Either this or `CustomerId` is required depending on tenant settings.            |
| `Name`          | `string` | 100        | ❌              | Customer's first name. Used in email personalization and campaign targeting.                               |
| `Surname`       | `string` | 300        | ❌              | Customer's last name. Combined with `Name` for full personalization.                                       |
| `Phone`         | `string` | 20         | ❌              | Customer's phone number. Required for SMS campaigns. Can include country code.                             |
| `Language`      | `string` | 10         | ❌              | Preferred language using IETF BCP 47 format (e.g., `en-US`, `fr-FR`). Determines campaign language.        |
| `Username`      | `string` | 500        | ❌              | Customer's username in your system. Useful for app-based businesses.                                       |
| `EmailOptin`    | `bool`   | -          | ❌              | Whether customer consented to email marketing. Default: `false`. Required for email campaigns.             |
| `SmsOptin`      | `bool`   | -          | ❌              | Whether customer consented to SMS marketing. Default: `false`. Required for SMS campaigns.                 |
| `WhatsappOptin` | `bool`   | -          | ❌              | Whether customer consented to WhatsApp messaging. Default: `false`. Required for WhatsApp campaigns.       |
| `AppPushOptin`  | `bool`   | -          | ❌              | Whether customer consented to app push notifications. Default: `false`. Required for push campaigns.       |
| `GdprOptin`     | `bool`   | -          | ❌              | Whether customer provided GDPR consent. Default: `false`. Important for EU compliance.                     |
| `IsExcluded`    | `bool`   | -          | ❌              | Exclude this customer from all campaigns. Default: `false`. Useful for VIP manual-only communication.      |

### Field Notes

**Consent Fields** (`EmailOptin`, `SmsOptin`, etc.):

* These control which communication channels are enabled for this customer
* Always respect customer preferences - sending to non-opted-in customers may violate regulations
* Update these fields when customers change their preferences

**Language Field**:

* Use standard language codes like `en`, `de`, `fr`
* Or region-specific codes like `en-US`, `es-MX`, `pt-BR`
* If omitted, campaigns will use your default language
* See [Best Practices - Language Standards](/replenit-docs/best-practices.md#language-format-standards) for complete guide

**GDPR Compliance**:

* The `GdprOptin` field is critical for EU customers
* Set to `true` only after obtaining explicit consent
* When `false`, customer data is stored but marketing is restricted

***

## 🗑️ DELETE Endpoint

### 🔗 Delete Customer

```http
DELETE /customers/{tenantId}?customerId={customerId}&email={email}
```

The deletion is queued for downstream processing and applied idempotently. The endpoint returns success once the request is durably accepted, regardless of whether a matching customer exists.

#### 📋 Query Parameters

At least one of `customerId` or `email` must be provided. Both may be sent together; downstream consumers will use whichever identifier matches.

| Parameter    | Type     | Required       | Description                   |
| ------------ | -------- | -------------- | ----------------------------- |
| `tenantId`   | `GUID`   | ✅ Yes (path)   | The tenant identifier         |
| `customerId` | `string` | ⚠️ Conditional | The tenant-scoped customer ID |
| `email`      | `string` | ⚠️ Conditional | The customer email address    |

#### ✅ Success Response

* **Status**: `200 OK`
* **Body**: Response envelope echoing the identifier(s) accepted

```json
{
  "success": true,
  "message": "Customer removed.",
  "data": {
    "customerId": "C-123",
    "email": null,
    "deletedAt": "2024-12-22T14:02:49Z"
  }
}
```

#### ❌ Error Responses

| Status | Scenario                              | Example Response                                                                 |
| ------ | ------------------------------------- | -------------------------------------------------------------------------------- |
| `400`  | Neither customerId nor email provided | `{"success": false, "message": "Customer ID or email is required.", "data": {}}` |
| `404`  | Tenant not found                      | `{"success": false, "message": "Tenant not found.", "data": {}}`                 |
| `500`  | Server error                          | `{"success": false, "message": "Failed to delete customer.", "data": {}}`        |

**Note**: Tenant validation is performed automatically by middleware before reaching the endpoint. If the tenant doesn't exist, you'll receive a 404 immediately.

### 🚀 DELETE Examples

#### cURL — by customer ID

```bash
curl -X DELETE "https://api.replen.it/customers/{tenantId}?customerId=C-123" \
  -H 'x-replenit-auth-key: YOUR_BASE64_ACCESS_KEY'
```

#### cURL — by email

```bash
curl -X DELETE "https://api.replen.it/customers/{tenantId}?email=user%40example.com" \
  -H 'x-replenit-auth-key: YOUR_BASE64_ACCESS_KEY'
```

#### cURL — by both (more reliable matching downstream)

```bash
curl -X DELETE "https://api.replen.it/customers/{tenantId}?customerId=C-123&email=user%40example.com" \
  -H 'x-replenit-auth-key: YOUR_BASE64_ACCESS_KEY'
```

#### Python

```python
import requests

url = f"https://api.replen.it/customers/{tenant_id}"
headers = {"x-replenit-auth-key": "YOUR_BASE64_ACCESS_KEY"}

# Either or both
response = requests.delete(url, headers=headers, params={
    "customerId": "C-123",
    "email": "user@example.com",
})
print(response.json())
```

#### Node.js

```javascript
const axios = require('axios');

axios.delete(`https://api.replen.it/customers/${tenantId}`, {
    headers: { 'x-replenit-auth-key': 'YOUR_BASE64_ACCESS_KEY' },
    params: { customerId: 'C-123', email: 'user@example.com' }
})
    .then(response => console.log(response.data))
    .catch(error => console.error(error));
```

***

## 💡 Real-World Use Cases

### Use Case 1: E-commerce Customer Signup

**Scenario**: Customer creates account on your website (no purchase yet)

**What to send**:

```json
[{
  "CustomerId": "WEB-2024-12345",
  "Email": "newcustomer@example.com",
  "Name": "Sarah",
  "Surname": "Johnson",
  "Language": "en-US",
  "EmailOptin": true,
  "GdprOptin": true
}]
```

**What happens**:

1. ✅ Customer profile created in Replenit
2. 🎯 Added to "Welcome" campaign
3. 📧 Receives welcome email series

***

### Use Case 2: Newsletter Subscription

**Scenario**: User subscribes to newsletter without creating account

**What to send**:

```json
[{
  "Email": "subscriber@example.com",
  "EmailOptin": true,
  "GdprOptin": true,
  "Language": "de-DE"
}]
```

**Why minimal fields**: You only have email, that's enough!

***

### Use Case 3: GDPR Opt-Out

**Scenario**: Customer requests to stop receiving emails

**What to send**:

```json
[{
  "CustomerId": "C-123",
  "EmailOptin": false,
  "SmsOptin": false,
  "WhatsappOptin": false
}]
```

**What happens**:

1. ✅ Customer preferences updated
2. 🚫 Removed from all marketing campaigns
3. ✅ GDPR compliance maintained

***

### Use Case 4: Bulk Customer Import

**Scenario**: Migrating 10,000 customers from old system

**Best practice**:

```python
# Batch in chunks of 500
for i in range(0, len(customers), 500):
    batch = customers[i:i+500]
    response = requests.post(url, json=batch)
    time.sleep(0.5)  # Rate limiting
```

**Performance**: \~20 batches = \~10 seconds total (vs 10,000 individual calls = 30+ minutes)

***

### 📝 Complete Example Request (All Fields)

```json
[
  {
    "CustomerId": "C-123",
    "Email": "john.doe@example.com",
    "Name": "John",
    "Surname": "Doe",
    "Phone": "+1-555-123-4567",
    "Language": "en-US",
    "Username": "johndoe",
    "EmailOptin": true,
    "SmsOptin": false,
    "WhatsappOptin": true,
    "AppPushOptin": true,
    "GdprOptin": true,
    "IsExcluded": false
  },
  {
    "CustomerId": "C-124",
    "Email": "jane.smith@example.com",
    "Name": "Jane",
    "Surname": "Smith",
    "Phone": "+44-20-1234-5678",
    "Language": "en-GB",
    "Username": "janesmith",
    "EmailOptin": true,
    "SmsOptin": true,
    "WhatsappOptin": false,
    "AppPushOptin": false,
    "GdprOptin": true,
    "IsExcluded": false
  },
  {
    "CustomerId": "C-125",
    "Email": "pedro.garcia@ejemplo.es",
    "Name": "Pedro",
    "Surname": "García",
    "Phone": "+34-91-123-4567",
    "Language": "es-ES",
    "Username": "pedrogarcia",
    "EmailOptin": false,
    "SmsOptin": false,
    "WhatsappOptin": true,
    "AppPushOptin": true,
    "GdprOptin": true,
    "IsExcluded": true
  }
]
```

### 📝 Minimal Example Request (Required Fields Only)

```json
[
  {
    "CustomerId": "C-126"
  }
]
```

### 📝 Email-Based Customer (When Priority is Email)

```json
[
  {
    "Email": "customer@example.com",
    "Language": "fr-FR",
    "GdprOptin": true,
    "Name": "Marie",
    "Surname": "Dubois"
  }
]
```

### 🚀 cURL Example

```bash
curl -sS \
  -X POST "https://api.replen.it/customers/{tenantId}" \
  -H 'Content-Type: application/json' \
  -H 'x-replenit-auth-key: YOUR_BASE64_ACCESS_KEY' \
  -d @customers.json
```

### 🐍 Python Example

```python
import requests
import json

url = "https://api.replen.it/customers/{tenantId}"
headers = {
    "Content-Type": "application/json",
    "x-replenit-auth-key": "YOUR_BASE64_ACCESS_KEY"
}

customers = [
    {
        "CustomerId": "C-123",
        "Email": "john.doe@example.com",
        "Name": "John",
        "Surname": "Doe",
        "EmailOptin": True
    }
]

response = requests.post(url, headers=headers, json=customers)
print(response.json())
```

### 🟢 Node.js Example

```javascript
const axios = require('axios');

const url = 'https://api.replen.it/customers/{tenantId}';
const headers = {
    'Content-Type': 'application/json',
    'x-replenit-auth-key': 'YOUR_BASE64_ACCESS_KEY'
};

const customers = [
    {
        CustomerId: 'C-123',
        Email: 'john.doe@example.com',
        Name: 'John',
        Surname: 'Doe',
        EmailOptin: true
    }
];

axios.post(url, customers, { headers })
    .then(response => console.log(response.data))
    .catch(error => console.error(error));
```

***

## 📊 Response Examples

### ✅ Success Response (200)

```json
{
  "success": true,
  "message": "Customers saved.",
  "data": {
    "count": 3,
    "processedAt": "2024-12-22T14:05:51Z"
  }
}
```

### ❌ Validation Error (400) - Missing Identifier (CustomerId or Email)

```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-abc123...",
  "errors": {
    "": [
      "At least one of the following properties must have a value: CustomerId, Email"
    ]
  }
}
```

**📖 What this means:**

* You must provide at least one identifier: either `CustomerId` OR `Email`
* This validation ensures we can identify the customer in our system
* Both can be provided for better data quality

**✅ How to fix:**

```json
[
  {
    "CustomerId": "C-123",
    "Name": "John",
    "Surname": "Doe"
  }
]
```

Or with email:

```json
[
  {
    "Email": "customer@example.com",
    "Name": "John",
    "Surname": "Doe"
  }
]
```

Best practice (both identifiers):

```json
[
  {
    "CustomerId": "C-123",
    "Email": "customer@example.com",
    "Name": "John",
    "Surname": "Doe"
  }
]
```

**💡 Common causes:**

* Sending empty customer object `{}`
* Both `CustomerId` and `Email` are null, empty string, or omitted
* Whitespace-only values in identifier fields

***

### ❌ Validation Error (400) - Missing Required Fields

```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-abc123...",
  "errors": {
    "[0].Language": [
      "The Language field is required."
    ],
    "[0].GdprOptin": [
      "The GdprOptin field is required."
    ]
  }
}
```

**📖 What this means:**

* Your first customer (index `[0]`) is missing required fields
* Note: These fields are only required if your tenant configuration enforces them
* Check your tenant settings to see which fields are mandatory

**✅ How to fix:**

```json
[
  {
    "CustomerId": "C-123",
    "Email": "customer@example.com",
    "Language": "en-US",
    "GdprOptin": true
  }
]
```

**💡 Common causes:**

* Missing `Language` field (use IETF BCP 47 format: `en-US`, `de-DE`, etc.)
* Missing `GdprOptin` field (must be `true` or `false`, not omitted)
* Tenant configuration requires these fields

### ❌ Validation Error (400) - String Length Exceeded

```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-def456...",
  "errors": {
    "[0].CustomerId": [
      "The field CustomerId must be a string with a maximum length of 100."
    ],
    "[0].Language": [
      "The field Language must be a string with a maximum length of 10."
    ]
  }
}
```

**📖 What this means:**

* Field values exceed maximum allowed length
* `CustomerId`: maximum 100 characters
* `Language`: maximum 10 characters

**✅ How to fix:**

* Use shorter customer IDs
* Use standard language codes (e.g., `en-US` instead of long text)

**💡 Field length limits:**

| Field                     | Max Length |
| ------------------------- | ---------- |
| CustomerId, Name, Surname | 100        |
| Username                  | 500        |
| Email                     | 255        |
| Phone                     | 20         |
| Language                  | 10         |

### ❌ Not Found (404)

```json
{
  "success": false,
  "message": "Tenant not found.",
  "data": {}
}
```

### ❌ Internal Server Error (500)

```json
{
  "success": false,
  "message": "An unexpected error occurred. Please try again.",
  "data": {}
}
```

***

## 🔁 Best Practices

1. **Batch Operations**: Send multiple customers in a single request for better performance (recommended batch size: 100-500)
2. **Validation**: Ensure email addresses are valid before submission
3. **GDPR Compliance**: Always obtain proper consent before setting opt-in flags
4. **Error Handling**: Implement retry logic for 500 errors with exponential backoff
5. **Rate Limiting**: Respect API rate limits to avoid throttling
6. **CustomerId**: Please ensure that it is also set as an identifier in your Marketing Automation platform, so users can be consistently matched across systems and events
7. **Language Format**: Use IETF language tags (BCP 47) format:
   * Examples: `en`, `en-US`, `en-GB`, `es-ES`, `fr-FR`, `de-DE`, `pt-BR`
   * Pattern: `language[-script][-region]`
   * Reference: [IETF BCP 47](https://tools.ietf.org/html/bcp47)

***

## 🔍 Related Resources

* [Products API Documentation](/replenit-docs/products.md)
* [Orders API Documentation](/replenit-docs/orders.md)
* [Error Responses Guide](/replenit-docs/error-responses.md)
* [Best Practices Guide](/replenit-docs/best-practices.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://replenit.gitbook.io/replenit-docs/customers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
