# Contacts

Your contacts: the fields they carry, the list, create or update in batches, delete, and find by phone number.

## List contact fields

`GET /v1/contacts/fields`

The fields your contacts carry, in display order. A field's `name` is the key in every contact's `fields`, and the name to use when you create or update contacts.

`is_match` fields find an existing contact on create. `is_phone` fields hold phone numbers: we keep digits only, and several numbers are separated by `;`. A field tied to one of your integrations carries `integration`.

### Request

**curl**

```bash
curl https://api.vocatech.com/v1/contacts/fields \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Node**

```javascript
const res = await fetch("https://api.vocatech.com/v1/contacts/fields", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.get(
    "https://api.vocatech.com/v1/contacts/fields",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->get('/v1/contacts/fields');

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Answers

#### 200

Your contact fields.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `fields` | array of objects | yes |  |
| `fields[].id` | integer | yes |  |
| `fields[].name` | string | yes | The field's name: the key in every contact's `fields`. |
| `fields[].order` | integer | yes | Display order. |
| `fields[].is_phone` | boolean | yes | Holds phone numbers. We keep digits only; several numbers are separated by `;`. |
| `fields[].is_match` | boolean | yes | Used to find an existing contact on create. A contact whose match fields all equal yours is updated instead. |
| `fields[].reports_enabled` | boolean | yes | A display setting from the portal. |
| `fields[].hide_no_data` | boolean | yes | A display setting from the portal. |
| `fields[].show_in_fields` | boolean | yes | A display setting from the portal. |
| `fields[].is_integration` | boolean | yes | True when the field is tied to one of your integrations. |
| `fields[].integration` | object |  | Present only on integration fields: the integration's settings, as set in the portal. |
| `fields[].integration.id` | integer |  |  |
| `fields[].integration.initials` | string |  |  |
| `fields[].integration.image_url` | string |  |  |
| `fields[].integration.fields` | array of objects |  |  |
| `fields[].integration.fields[].key` | string |  |  |
| `fields[].integration.fields[].display_name` | string |  |  |
| `fields[].integration.fields[].value` | string |  |  |
| `fields[].integration.fields[].fills` | array, each any |  | Integration settings, as set in the portal. |

**Example**

```json
{
  "fields": [
    {
      "id": 31,
      "name": "First Name",
      "order": 1,
      "is_phone": false,
      "is_match": false,
      "reports_enabled": true,
      "hide_no_data": false,
      "show_in_fields": true,
      "is_integration": false
    },
    {
      "id": 32,
      "name": "Last Name",
      "order": 2,
      "is_phone": false,
      "is_match": false,
      "reports_enabled": true,
      "hide_no_data": false,
      "show_in_fields": true,
      "is_integration": false
    },
    {
      "id": 33,
      "name": "Phone",
      "order": 3,
      "is_phone": true,
      "is_match": true,
      "reports_enabled": true,
      "hide_no_data": false,
      "show_in_fields": true,
      "is_integration": false
    },
    {
      "id": 34,
      "name": "Email",
      "order": 4,
      "is_phone": false,
      "is_match": false,
      "reports_enabled": false,
      "hide_no_data": true,
      "show_in_fields": true,
      "is_integration": false
    },
    {
      "id": 35,
      "name": "Account Number",
      "order": 5,
      "is_phone": false,
      "is_match": true,
      "reports_enabled": false,
      "hide_no_data": true,
      "show_in_fields": true,
      "is_integration": false
    }
  ]
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 429

A rate limit was reached. Wait `Retry-After` seconds. The body names the counter, the window and the limit.

#### 503

Try again shortly. The key could not be checked just now (`Retry-After: 5`), or a service this route needs could not be reached.

## List contacts

`GET /v1/contacts`

Your contacts, oldest first. `search` finds contacts where any field contains the text.

### Request

**curl**

```bash
curl -G https://api.vocatech.com/v1/contacts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "page=1" \
  --data-urlencode "limit=100"
```

**Node**

```javascript
const params = new URLSearchParams({
  page: "1",
  limit: "100",
});

const res = await fetch(`https://api.vocatech.com/v1/contacts?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.get(
    "https://api.vocatech.com/v1/contacts",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={
        "page": 1,
        "limit": 100,
    },
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->get('/v1/contacts', [
    'query' => [
        'page' => 1,
        'limit' => 100,
    ],
]);

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer |  | Page number, from 1. Default `1`. At least 1. |
| `limit` | query | integer |  | Contacts per page, 1 to 500. A value outside that range is brought inside it. Default `100`. From 1 to 500. |
| `search` | query | string |  | Text to find in any field. For example `Reyes`. |

### Answers

#### 200

A page of contacts.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | object | yes |  |
| `query.page` | integer | yes |  |
| `query.limit` | integer | yes |  |
| `query.search` | string or null | yes |  |
| `contacts` | array of objects | yes |  |
| `contacts[].id` | integer | yes | The contact's id. |
| `contacts[].fields` | object | yes | Field name to value. The names are your contact fields (`GET /v1/contacts/fields`). Phone fields hold digits; several numbers are separated by `;`. |
| `contacts[].fields.{field}` | string or null |  | One value for each field, by its name. |
| `meta` | object | yes | Paging for list routes. Each list adds its own total. |
| `meta.page` | integer | yes | This page, from 1. |
| `meta.limit` | integer | yes | Rows per page, after limits were applied. |
| `meta.total_pages` | integer | yes | How many pages there are. |
| `meta.total_contacts` | integer | yes | Contacts that match. `total_pages` is 0 when there are none. |

**Example**

```json
{
  "query": {
    "page": 1,
    "limit": 100,
    "search": null
  },
  "contacts": [
    {
      "id": 5012,
      "fields": {
        "First Name": "Jordan",
        "Last Name": "Reyes",
        "Phone": "7185550142",
        "Email": "jordan.reyes@example.com",
        "Account Number": "A-2291"
      }
    },
    {
      "id": 5013,
      "fields": {
        "First Name": "Sam",
        "Last Name": "Patel",
        "Phone": "7185550143;9175550123",
        "Email": "sam.patel@example.com",
        "Account Number": "A-2310"
      }
    }
  ],
  "meta": {
    "page": 1,
    "limit": 100,
    "total_pages": 1,
    "total_contacts": 2
  }
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 429

A rate limit was reached. Wait `Retry-After` seconds. The body names the counter, the window and the limit.

#### 503

Try again shortly. The key could not be checked just now (`Retry-After: 5`), or a service this route needs could not be reached.

## Create or update contacts

`POST /v1/contacts`

Creates a contact, or updates the one that matches. Send one contact as `{"fields": {...}}`, or up to 500 as `{"contacts": [{"fields": {...}}]}`.

`fields` maps field names (from `GET /v1/contacts/fields`) to values. Names we do not know are ignored. Values over 255 characters are cut. Phone fields keep digits only; separate several numbers with `;`.

**Matching.** When your account has match fields (`is_match: true`), a contact whose match fields all equal yours is updated instead of created. With no match fields, every contact is created new.

One contact answers `201` when created and `200` when updated. A batch always answers `200`. One bad entry does not stop the rest: `errors` lists each one by its `index` in your array.

### Request

**curl**

```bash
curl -X POST https://api.vocatech.com/v1/contacts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "First Name": "Jordan",
      "Last Name": "Reyes",
      "Phone": "(718) 555-0142",
      "Email": "jordan.reyes@example.com",
      "Account Number": "A-2291"
    }
  }'
```

**Node**

```javascript
const res = await fetch("https://api.vocatech.com/v1/contacts", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    fields: {
      "First Name": "Jordan",
      "Last Name": "Reyes",
      Phone: "(718) 555-0142",
      Email: "jordan.reyes@example.com",
      "Account Number": "A-2291",
    },
  }),
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.post(
    "https://api.vocatech.com/v1/contacts",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "fields": {
            "First Name": "Jordan",
            "Last Name": "Reyes",
            "Phone": "(718) 555-0142",
            "Email": "jordan.reyes@example.com",
            "Account Number": "A-2291",
        },
    },
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->post('/v1/contacts', [
    'json' => [
        'fields' => [
            'First Name' => 'Jordan',
            'Last Name' => 'Reyes',
            'Phone' => '(718) 555-0142',
            'Email' => 'jordan.reyes@example.com',
            'Account Number' => 'A-2291',
        ],
    ],
]);

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Body

**One contact**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `fields` | object | yes | Field name to value. Names come from `GET /v1/contacts/fields`; unknown names are ignored. Values over 255 characters are cut. At least 1 field. |
| `fields.{field}` | string or null |  | One value for each field, by its name. |

**A batch**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `contacts` | array of objects | yes | Up to 500 items. |
| `contacts[].fields` | object | yes | Field name to value. Names come from `GET /v1/contacts/fields`; unknown names are ignored. Values over 255 characters are cut. At least 1 field. |
| `contacts[].fields.{field}` | string or null |  | One value for each field, by its name. |

**One contact**

```json
{
  "fields": {
    "First Name": "Jordan",
    "Last Name": "Reyes",
    "Phone": "(718) 555-0142",
    "Email": "jordan.reyes@example.com",
    "Account Number": "A-2291"
  }
}
```

**A batch**

```json
{
  "contacts": [
    {
      "fields": {
        "First Name": "Jordan",
        "Last Name": "Reyes",
        "Phone": "7185550142",
        "Account Number": "A-2291"
      }
    },
    {
      "fields": {
        "First Name": "Sam",
        "Last Name": "Patel",
        "Phone": "7185550143;9175550123",
        "Account Number": "A-2310"
      }
    }
  ]
}
```

### Answers

#### 200

One contact was updated, or a batch was processed.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `summary` | object | yes |  |
| `summary.total` | integer | yes |  |
| `summary.created` | integer | yes |  |
| `summary.updated` | integer | yes |  |
| `summary.errors` | integer | yes |  |
| `contacts` | array of objects | yes |  |
| `contacts[].contact` | object | yes |  |
| `contacts[].contact.id` | integer | yes | The contact's id. |
| `contacts[].contact.fields` | object | yes | Field name to value. The names are your contact fields (`GET /v1/contacts/fields`). Phone fields hold digits; several numbers are separated by `;`. |
| `contacts[].contact.fields.{field}` | string or null |  | One value for each field, by its name. |
| `contacts[].action` | string | yes | One of `created` or `updated`. |
| `errors` | array of objects | yes |  |
| `errors[].index` | integer | yes | The entry's position in your `contacts` array, from 0. |
| `errors[].message` | string | yes |  |

**Example**

```json
{
  "summary": {
    "total": 2,
    "created": 1,
    "updated": 1,
    "errors": 0
  },
  "contacts": [
    {
      "contact": {
        "id": 5012,
        "fields": {
          "First Name": "Jordan",
          "Last Name": "Reyes",
          "Phone": "7185550142",
          "Email": "jordan.reyes@example.com",
          "Account Number": "A-2291"
        }
      },
      "action": "updated"
    },
    {
      "contact": {
        "id": 5013,
        "fields": {
          "First Name": "Sam",
          "Last Name": "Patel",
          "Phone": "7185550143;9175550123",
          "Email": "sam.patel@example.com",
          "Account Number": "A-2310"
        }
      },
      "action": "created"
    }
  ],
  "errors": []
}
```

#### 201

One contact was created.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `summary` | object | yes |  |
| `summary.total` | integer | yes |  |
| `summary.created` | integer | yes |  |
| `summary.updated` | integer | yes |  |
| `summary.errors` | integer | yes |  |
| `contacts` | array of objects | yes |  |
| `contacts[].contact` | object | yes |  |
| `contacts[].contact.id` | integer | yes | The contact's id. |
| `contacts[].contact.fields` | object | yes | Field name to value. The names are your contact fields (`GET /v1/contacts/fields`). Phone fields hold digits; several numbers are separated by `;`. |
| `contacts[].contact.fields.{field}` | string or null |  | One value for each field, by its name. |
| `contacts[].action` | string | yes | One of `created` or `updated`. |
| `errors` | array of objects | yes |  |
| `errors[].index` | integer | yes | The entry's position in your `contacts` array, from 0. |
| `errors[].message` | string | yes |  |

**Example**

```json
{
  "summary": {
    "total": 1,
    "created": 1,
    "updated": 0,
    "errors": 0
  },
  "contacts": [
    {
      "contact": {
        "id": 5012,
        "fields": {
          "First Name": "Jordan",
          "Last Name": "Reyes",
          "Phone": "7185550142",
          "Email": "jordan.reyes@example.com",
          "Account Number": "A-2291"
        }
      },
      "action": "created"
    }
  ],
  "errors": []
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 422

No `fields` object, or a batch over 500.

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "Missing or invalid \"fields\" object."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "Batch size cannot exceed 500 contacts."
  }
}
```

#### 429

A rate limit was reached. Wait `Retry-After` seconds. The body names the counter, the window and the limit.

#### 503

Try again shortly. The key could not be checked just now (`Retry-After: 5`), or a service this route needs could not be reached.

## Delete contacts

`DELETE /v1/contacts`

Deletes one contact (`{"id": 5012}`) or up to 500 (`{"ids": [5012, 5013]}`). The ids go in a JSON body.

One contact that is not yours or does not exist answers `404`. In a batch it is listed in `errors`, and the rest are deleted.

### Request

**curl**

```bash
curl -X DELETE https://api.vocatech.com/v1/contacts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 5012
  }'
```

**Node**

```javascript
const res = await fetch("https://api.vocatech.com/v1/contacts", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    id: 5012,
  }),
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.delete(
    "https://api.vocatech.com/v1/contacts",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "id": 5012,
    },
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->delete('/v1/contacts', [
    'json' => [
        'id' => 5012,
    ],
]);

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Body

**One contact**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | integer | yes |  |

**A batch**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ids` | array of integers | yes | Up to 500 items. |

**One contact**

```json
{
  "id": 5012
}
```

**A batch**

```json
{
  "ids": [
    5012,
    5013,
    5099
  ]
}
```

### Answers

#### 200

Deleted.

**One contact**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | integer | yes | The id that was deleted. |

**A batch**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `summary` | object | yes |  |
| `summary.total` | integer | yes |  |
| `summary.deleted` | integer | yes |  |
| `summary.errors` | integer | yes |  |
| `deleted` | array of integers | yes | The ids that were deleted. |
| `errors` | array of objects | yes | Ids not found on your account. |
| `errors[].id` | integer | yes |  |
| `errors[].message` | string | yes |  |

**One contact**

```json
{
  "deleted": 5012
}
```

**A batch**

```json
{
  "summary": {
    "total": 3,
    "deleted": 2,
    "errors": 1
  },
  "deleted": [
    5012,
    5013
  ],
  "errors": [
    {
      "id": 5099,
      "message": "The contact you requested does not exist."
    }
  ]
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 404

One contact: not found on your account.

**Example**

```json
{
  "error": {
    "code": 404,
    "type": "NOT_FOUND",
    "message": "The contact you requested does not exist."
  }
}
```

#### 422

No `id`, or a batch over 500.

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "Missing \"id\" field."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "Batch size cannot exceed 500 IDs."
  }
}
```

#### 429

A rate limit was reached. Wait `Retry-After` seconds. The body names the counter, the window and the limit.

#### 503

Try again shortly. The key could not be checked just now (`Retry-After: 5`), or a service this route needs could not be reached.

## Find contacts by phone number

`GET /v1/contacts/lookup`

The contacts a phone number belongs to, up to 10, through the same phone index Callpop uses. Any format works on either side: `(718) 555-0142`, `+17185550142` and `7185550142` find the same contact.

Keys with the `agents` scope can call this route.

### Request

**curl**

```bash
curl -G https://api.vocatech.com/v1/contacts/lookup \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "phone=7185550142"
```

**Node**

```javascript
const params = new URLSearchParams({
  phone: "7185550142",
});

const res = await fetch(`https://api.vocatech.com/v1/contacts/lookup?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.get(
    "https://api.vocatech.com/v1/contacts/lookup",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={
        "phone": "7185550142",
    },
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->get('/v1/contacts/lookup', [
    'query' => [
        'phone' => '7185550142',
    ],
]);

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `phone` | query | string | yes | The phone number, any format, at least 7 digits. For example `7185550142`. |

### Answers

#### 200

The contacts with that number. `found` is false when there are none.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `phone` | string | yes | The number you asked about, digits only, without a leading 1. |
| `found` | boolean | yes |  |
| `contacts` | array of objects | yes | Up to 10. Up to 10 items. |
| `contacts[].id` | integer | yes | The contact's id. |
| `contacts[].fields` | object | yes | Field name to value. The names are your contact fields (`GET /v1/contacts/fields`). Phone fields hold digits; several numbers are separated by `;`. |
| `contacts[].fields.{field}` | string or null |  | One value for each field, by its name. |

**Example**

```json
{
  "phone": "7185550142",
  "found": true,
  "contacts": [
    {
      "id": 5012,
      "fields": {
        "First Name": "Jordan",
        "Last Name": "Reyes",
        "Phone": "7185550142",
        "Email": "jordan.reyes@example.com",
        "Account Number": "A-2291"
      }
    }
  ]
}
```

#### 400

No phone, or fewer than 7 digits.

**Example**

```json
{
  "error": {
    "code": 400,
    "type": "BAD_REQUEST",
    "message": "phone is required and must contain at least 7 digits."
  }
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 429

A rate limit was reached. Wait `Retry-After` seconds. The body names the counter, the window and the limit.

#### 503

Try again shortly. The key could not be checked just now (`Retry-After: 5`), or a service this route needs could not be reached.
