# Contacts

The `/contacts` endpoint allows you to view and modify your Dripcel [contacts](https://docs.dripcel.com/concepts/contacts).

## **Get a single contact**

`GET /contacts/:cell`

Get a single contact by their cell number.

* 🔐 Permissions: `["contact.read.pii"]`

### **Request**

**URL Parameters**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `cell` | `string` | ✅        | The MSISDN of the contact you want to view. |

### **Response**

**Success**

**200**

```javascript
{
  ok: true,
  data: Contact
}
```

**Error**

**400**

```javascript
{
  ok: false;
  error: "Invalid cell number";
}
```

**404**

```javascript
{
  ok: false;
  error: "Contact not found";
}
```

## **Search for contacts**

`POST /contacts/search`

Run a search query on your contacts.

* 🔐 Permissions: `["contact.read.pii"]`
* 💵 Paid endpoint: 10 credits per request

### **Request**

**Body**


:::success
**tip**

The `find`, `projection`, and `options` fields all use (a subset of) MongoDB query syntax. You can find more information in the [MongoDB documentation](https://docs.mongodb.com/manual/tutorial/query-documents/).

:::

```javascript
type ContactSearchBody = {
  /** The search query. All fields are optional, but atleast one filter needs to be passed */
  find?: {
    /** The cell number of the contact */
    cell?: string[];
    /** The tags of the contact */
    tag_ids?: {
      /** Matches if ANY of the tags are present */
      $in?: string[];
      /** Matches if ALL of the tags are present */
      $all?: string[];
    };
    /** The date the contact was last updated */
    updatedAt?: {
      /** Minimum date */
      $gte?: Date;
      /** Maximum date */
      $lte?: Date;
    };
  };
  /** The projection of the search results. By default, only the contact_id is returned.
   * If you want to return specific fields, you can specify them here.
   */
  projection?: Record<ContactField, 1>;

  options?: {
    /** The number of results to skip */
    skip?: number;
    /** The number of results to return */
    limit?: number;
  };
};
```

**Example**

```javascript
{
  "find": {
    "cell": ["0821234567", "0827654321"],
    "tag_ids": {
      "$in": ["tag_id_1", "tag_id_2"]
    },
    "updatedAt": {
      "$gte": "2021-01-01T00:00:00.000Z",
      "$lte": "2021-12-31T23:59:59.999Z"
    }
  },
  "projection": {
    "cell": 1,
    "firstname": 1,
    "c1": 1
  },
  "options": {
    "skip": 0,
    "limit": 10
  }
}
```

### **Response**


:::success
**Success**

:::

**200**

```javascript
{
  ok: true;
  data: {
    /** The parsed search query, with default values filled in */
    parsed_request: ContactSearchBody;
    /** The contacts that matched the search query */
    contacts: Contact[];
  }
}
```


:::warning
**Error**

:::

**400**

```javascript
{
  ok: false;
  /** Contains detailed information about the validation errors */
  error: ZodIssue[];
}
```

**403**

```javascript
{
  ok: false;
  error: {
    code: "Insufficient Resource IDs";
    /** The missing resource IDs */
    missing: Record<string, string[]>;
  }
}
```

## **Delete a single contact**

`DELETE /contacts/:cell`

Delete a single contact by their cell number.

* 🔐 Permissions: `["contact.delete"]`

### **Request**

**URL Parameters**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `cell` | `string` | ✅        | The MSISDN of the contact you want to delete. |

### **Response**


:::success
**Success**

:::

**200**

```javascript
{
  ok: true;
}
```


:::warning
**Error**

:::

**400**

```javascript
{
  ok: false;
  error: "Invalid cell number";
}
```

**404**

```javascript
{
  ok: false;
  error: "Contact not found";
}
```

## **Opt out a contact**


:::info
**note**

The `POST` endpoint is more robust. It allows you to opt a contact out of **multiple** campaigns at once.

The `PUT` endpoint only allows you to opt a contact out of a *single* campaign.

:::

`POST /contacts/:cell/optOut`

Opt out a contact by their cell number.

* 🔐 Permissions: `["contact.update.deduped_campaign_ids"]`

### **Request**

**URL Parameters**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `cell` | `string` | ✅        | The MSISDN of the contact you want to optOut. |

**Body**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `campaign_ids` | `string[]` | ❌        | The ids of the [campaigns](https://docs.dripcel.com/concepts/campaigns) you want to opt out the contact from. |
| `all` | `boolean` | ❌        | Opt out the contact from all existing and **future** campaigns. |
| `create_missing_contact` | `boolean` | ❌        | If the contact doesn't exist, create it with the given opt-outs. set. |

### **Response**


:::success
**Success**

:::

**200**

```javascript
{
  ok: true;
  data: {
    matchedCount: number; // The number of contacts that matched the cell number
    modifiedCount: number; // The number of contacts that were modified
  }
}
```


:::warning
**Error**

:::

**400**

```javascript
{
  ok: false;
  error: "Invalid cell number";
}
```

**400**

```javascript
{
  ok: false;
  error: "Must provider either campaign_ids or set 'all' flag to true";
}
```


---

`PUT /contacts/:cell/optOut`

Opt out a contact by their cell number.

* 🔐 Permissions: `["contact.update.deduped_campaign_ids"]`

### **Request**

**URL Parameters**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `cell` | `string` | ✅        | The MSISDN of the contact you want to optOut. |

**Body**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `campaign_id` | `string` | ✅        | The id of the [campaign](https://docs.dripcel.com/concepts/campaigns) you want to opt out the contact from. |
| `all` | `boolean` | ❌        | Opt out the contact from all existing and **future** campaigns. |

### **Response**


:::success
**Success**

:::

**200**

```javascript
{
  ok: true;
  data: {
    /** The number of contacts that matched the cell number */
    matchedCount: number;
    /** The number of contacts that were modified */
    modifiedCount: number;
  }
}
```


:::warning
**Error**

:::

**400**

```javascript
{
  ok: false;
  error: "Invalid cell number";
}
```

## **Add or remove tags from a contact**

Add or remove tags from a contact by their cell number.


:::success
**tip**

Using the two endpoints below, you can add or remove tags from an existing contact by their cell number.

* `PUT /contacts/:cell/tag/add`
* `PUT /contacts/:cell/tag/remove`

Both routes take the same input, and return the same output.

:::

* 🔐 Permissions: `["contact.update.tag_ids"]`

### **Request**

**URL Parameters**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `cell` | `string` | ✅        | The MSISDN of the contact you want to add/remove tags from. |

**Body**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `tag_ids` | `string[]` | ❌        | A list of [tag](https://docs.dripcel.com/concepts/tags) ids you want to add/remove |
| `tags` | `string[]` | ❌        | A list of [tag](https://docs.dripcel.com/concepts/tags) names you want to add/remove. It is recommended to use `tag_ids` instead, as tag names can change. |
| `create_missing_contact` | `boolean` | ❌        | If the contact doesn't exist, create it with the given tags set. Only applies to `/contacts/[cell]/tag/add`. |

**Example**

```javascript
{
  "tag_ids": ["tag_id_1", "tag_id_2"],
  "tags": ["tag_name_1", "tag_name_2"],
  "create_missing_contact": true
}
```

### **Response**


:::success
**Success**

:::

**200**

```javascript
{
  ok: true;
  data: {
    matchedCount: number; // The number of contacts that matched the cell number
    modifiedCount: number; // The number of contacts that were modified
  }
}
```


:::warning
**Error**

:::

**400**

```javascript
{
  ok: false;
  // If your request doesn't contain any tags, or none of the tags supplied are valid
  error: "No tags to add/remove";
}
```

## **Upload contacts**


:::success
**tip**

There are two contact-upload methods.

`POST /contacts`

* Will only create **new** contacts, and is relatively fast.
* Has a limit of 100_000 contacts per request.
* Uses fewer rate limit credits.

`PUT /contacts`

* Will create new contacts, and *update existing* contacts. It is slower than `POST /contacts`, but more flexible.
* Has a limit of 20_000 contacts per request.
* Uses more rate limit credits.

Both methods take the same input, and return the same output.

:::

`POST /contacts`, `PUT /contacts`

Upload a list of contacts to Dripcel.

* 🔐 Permissions: `["contact.create"]` (for `POST`), `["contact.update"]` (for `PUT`)

### **Request**

**Body**

| **Field** | **Type** | **Required** | **Description** |
|-------|------|----------|-------------|
| `contacts` | `Contact[]` | ✅        | An array of contacts to upload. The [default](https://docs.dripcel.com/concepts/contacts#fields) field names must be used, as well as the custom field ids (e.g. `c1`, `c2`). In other words, there is no field name mapping. You need to use the exact fields expected by the API. |
| `country` | `'ZA'` \| `'NA'` | ❌        | The country code of the contacts' cell numbers. If specific, you don't have to add the areacode. If not, the areacode is required for each cell. |
| `tag_ids` | `string[]` | ❌        | A list of [tags](https://docs.dripcel.com/concepts/tags) to add to **all** contacts. You can give different tags to *each* contact using the `tag_ids` field or `tags` field on each contact in the array. |
| `welcome_send_campaign_id` | `string` | ❌        | If present, trigger the [welcome send](https://docs.dripcel.com/concepts/campaigns#welcome-sends) of the given campaign. Messages will only be sent to contacts that were successfully uploaded. |

**Example**

```javascript
{
  // Parse each contact's `cell` number in South African format
  "country": "ZA",
  // Will be added to all contacts
  "tag_ids": ["680a0b0c0d0e0f0g0h0i0"],
  // Trigger the welcome send of this campaign
  "welcome_send_campaign_id": "689a0b0c0d0e0f0g0h0i0",
  // The contacts to upload
  "contacts": [
    {
      "cell": "0821234567",
      "firstname": "John",
      "c1": "value1",
      "c2": "value2",
      // Will only be added to this contact
      "tag_ids": ["680a0b0c0d0e0f0g0h0i1"]
    },
    {
      "cell": "0827654321",
      "firstname": "Jane",
      "c1": "value3",
      "c2": "value4",
      // You can also refer to tags by name
      "tags": ["tag3", "tag4"]
    }
  ]
}
```

### **Response**


:::success
**Success**

:::

**200**

```javascript
{
  ok: true;
  data: {
    // The number of contacts that passed validation
    validContacts: number;
    // The contacts that failed validation
    invalidContacts: ({
      row: number; // The 0-based index of the invalid contact in the array
      issue: ZodIssue; // The Zod validation error
    }[]);
  }
}
```

## **Bulk Update Contacts**

Run a bulk update on your contacts.

`POST /contacts/update`

* 🔐 Permissions: `["contact.update"]`


:::warning
**danger**

This endpoint is quite heavily rate-limited. Although you can update many contacts in one request, you can only make a limited number of requests per minute. Be sure to check for `429` responses and back-off if you hit the rate limit.

:::

### **Request**

**Body**


:::success
**tip**

The `find` and `update` fields use a subset of MongoDB query syntax. You can find more information in the [MongoDB documentation](https://docs.mongodb.com/manual/tutorial/query-documents/).

:::

```javascript
type ContactBulkUpdateBody = {
  // Which contacts to update
  find: {
    // Match contacts with these cell numbers
    cell?: string[];
    // Match contacts with these tags
    tag_ids?: {
      // Match if ANY of the tags are present
      $in?: string[];
      // Match if ALL of the tags are present
      $all?: string[];
    };
  };

  // The update to apply to the matching contacts
  // NOTE: Only one of the following fields can be present
  //       - you can't $addToSet and $pull in the same request
  update: {
    $addToSet?: {
      // Add these tags to the matching contacts
      tag_ids?: {
        $each: string[];
      };
      // Dedupe the matching contacts against these campaign_ids
      deduped_campaign_ids?: {
        $each: string[];
      };
    };

    $pull?: {
      // Remove these tags from the matching contacts
      tag_ids?: {
        $in: string[];
      };
    };
  };
};
```

**Example**

```javascript
{
  // Find contacts with these tags
  "find": {
    "tag_ids": {
      "$in": ["tag_id_1", "tag_id_2"]
    }
  },

  // Add these tags to the matching contacts
  "update": {
    "$addToSet": {
      "tag_ids": {
        "$each": ["tag_id_3", "tag_id_4"]
      }
    }
  }
}
```

### **Response**


:::success
**Success**

:::

**200**

```javascript
{
  ok: true;
  data: {
    // The number of contacts that matched the query
    matchedCount: number;
    // The number of contacts that were modified
    modifiedCount: number;
    // The parsed request. Useful to see what defaults were filled in, and which fields were actually used
    parsedRequest: ContactBulkUpdateBody;
  }
}
```


:::warning
**Error**

:::

**400**

```javascript
{
  ok: false;
  // Contains detailed information about the validation errors
  error: ZodError;
}
```

**403**

```javascript
{
  ok: false;
  error: {
    code: "insufficient_resource_ids";
    // The missing resource IDs
    missing: Record<string, string[]>;
  }
}
```