
# Contacts API

Een contactpersoon is een individu naar wie je berichten stuurt — hun naam, telefoonnummer, e-mailadres, kanaal, labels, aangepaste velden en de lijsten en campagnes waartoe ze behoren. Met de Contacts API kun je contactpersonen aanmaken, opzoeken, bijwerken, labelen, in bulk importeren en verwijderen, allemaal zonder het dashboard te gebruiken.

Alle paden op deze pagina zijn relatief ten opzichte van de basis-URL:

```
https://api.youraiconnector.com/v1
```

Dus `/contacts` betekent `https://api.youraiconnector.com/v1/contacts`.

> **Nieuw bij de API?** Lees eerst [API Access](../integrations/api-access.md) — hierin wordt uitgelegd hoe je je API-sleutel genereert, de drie manieren om te authenticeren, limieten voor het aantal verzoeken en de foutopmaak. Alles op deze pagina gaat ervan uit dat je al een werkende API-sleutel hebt.

---

## Over contact-ID's

Elke contactpersoon heeft een uniek ID. Het ID dat je terugkrijgt wanneer je een contactpersoon **aanmaakt** (in `data.contactId`) is hetzelfde ID dat je overal elders gebruikt — om die contactpersoon op te halen, bij te werken, te labelen, een bericht te sturen of te verwijderen. Sla het één keer op en hergebruik het.

Je hoeft geen contactpersoon aan te maken om het ID te krijgen. Je kunt er ook een opzoeken op telefoonnummer of e-mailadres (zie [Get a contact](#get-a-contact-by-phone-or-email)), of door al je contactpersonen bladeren (zie [List contacts](#list-contacts)). Elk van deze geeft hetzelfde ID terug.

---

## Een contactpersoon aanmaken

`POST /contacts`

Voegt een nieuwe contactpersoon toe aan je account. Een **telefoonnummer met landcode is vereist** — alleen een e-mailadres is niet voldoende. Al het andere is optioneel.

Je kunt de nieuwe contactpersoon optioneel direct in een of meer lijsten plaatsen met `listId` (één lijst) of `listIds` (een array). Als beide worden verzonden, wint `listIds`.

Elk veld dat u verstuurt en dat geen van de standaard aanmaakvelden is die in de onderstaande tabel **Contact aanmaken** staan (`phoneNumber`, `firstName`, `lastName`, `email`, `channel`, `is_bot_active`, `is_private`, `lead_profile`, `listId`, `listIds`, `custom_fields`), wordt automatisch opgeslagen als een **aangepast veld** — een platte payload van een tool zoals Make of Zapier werkt dus zonder nesting. U kunt ook een expliciet `custom_fields` object doorgeven.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `phoneNumber` | Ja | Het telefoonnummer van de contactpersoon, met landcode (bijv. `+15551234567`). |
| `firstName` | Nee | Voornaam. |
| `lastName` | Nee | Achternaam. |
| `email` | Nee | E-mailadres. |
| `channel` | Nee | Berichtkanaal. Een van `whatsapp`, `sms`, `whatsapp_web`. Standaard is `whatsapp`. |
| `is_bot_active` | Nee | Of de AI-assistent antwoordt op deze contactpersoon. Standaard is `true`. |
| `is_private` | Nee | Markeer de contactpersoon als privé. Wanneer `true`, is de AI-assistent voor hen uitgeschakeld. Standaard is `false`. |
| `lead_profile` | Nee | Vrije tekstnotities over de lead. |
| `listId` | Nee | Een enkel lijst-ID om de contactpersoon aan toe te voegen. |
| `listIds` | Nee | Een array van lijst-ID's om de contactpersoon aan toe te voegen (heeft voorrang op `listId`). |
| `custom_fields` | Nee | Een object met je eigen sleutel/waarde-velden. Je kunt deze ook doorgeven als top-level sleutels. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+15551234567",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "is_bot_active": true,
    "listIds": ["list123", "list456"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/contacts", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phoneNumber: "+15551234567",
    firstName: "Jane",
    lastName: "Smith",
    email: "jane@example.com",
    is_bot_active: true,
    listIds: ["list123", "list456"],
  }),
});
const data = await res.json();
console.log(data.data.contactId);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "phoneNumber": "+15551234567",
        "firstName": "Jane",
        "lastName": "Smith",
        "email": "jane@example.com",
        "is_bot_active": True,
        "listIds": ["list123", "list456"],
    },
)
print(res.json()["data"]["contactId"])
```

**Antwoord**

```json
{
  "success": true,
  "data": {
    "message": "Successfully created new contact",
    "contactId": "contact_abc123",
    "listsAdded": ["list123", "list456"]
  }
}
```

De ID van de nieuwe contactpersoon staat op `data.contactId`. De lijsten waaraan deze is toegevoegd, worden teruggegeven in `data.listsAdded`.

> **Er worden geen duplicaten aangemaakt.** Als er al een contact met hetzelfde telefoonnummer bestaat, maakt de aanroep voor aanmaken deze **niet** aan en wordt deze ook niet geretourneerd. Het antwoord komt terug met HTTP-status `200` en een `error_code` van `409` in de body, dus vertak op `error_code` in plaats van op de HTTP-status:
>
> ```json
> { "success": false, "error_code": 409, "error": "A contact with this phone number already exists for the current user." }
> ```
>
> Om met een bestaand contact te werken na een `error_code` van `409`, zoekt u het op met [Een contact ophalen op telefoonnummer of e-mailadres](#get-a-contact-by-phone-or-email) — `GET /contacts?phoneNumber=...` — en hergebruikt u de ID die dit teruggeeft.

> **Gelijkwaardige WhatsApp-spellingwijzen tellen als hetzelfde nummer.** Sommige landen hebben twee geldige spellingen voor dezelfde mobiele lijn en WhatsApp kan beide rapporteren: Mexico (`+52…` en de verouderde `+521…`), Brazilië (met of zonder het negende cijfer) en Argentinië (met of zonder de `9` na `+54`). De dubbelcheck bij aanmaak en `GET /contacts?phoneNumber=` komt overeen met beide spellingen, dus u krijgt de bestaande contactpersoon terug, ongeacht de vorm die u verstuurt. Het `phone_number` dat op de contactpersoon is opgeslagen, wordt nooit overschreven.

---

## Een contactpersoon opzoeken op telefoonnummer of e-mailadres

`GET /contacts?phoneNumber=...` of `GET /contacts?email=...`

Zoekt één contactpersoon op en retourneert het volledige, verrijkte contactobject — inclusief de lijsten, tags en campagnes opgelost naar `{ id, name }`-paren, plus het laatst uitgewisselde bericht.

Geef **ofwel** `phoneNumber` (in internationaal formaat) **ofwel** `email` op. Als je geen van beide opgeeft, schakelt dit eindpunt over naar de modus [Contactpersonen vermelden](#list-contacts).

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?phoneNumber=%2B15551234567&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const phone = encodeURIComponent("+15551234567");
const res = await fetch(`https://api.youraiconnector.com/v1/contacts?phoneNumber=${phone}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.contact);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"phoneNumber": "+15551234567"},
)
print(res.json()["contact"])
```

**Antwoord**

```json
{
  "success": true,
  "contactId": "contact_abc123",
  "contact": {
    "id": "contact_abc123",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "phoneNumber": "+15551234567",
    "channel": "whatsapp",
    "isBotActive": true,
    "isPrivate": false,
    "doNotDisturb": false,
    "lead_profile": null,
    "avatarUrl": "https://example.com/photo.jpg",
    "customFields": {},
    "lists": [{ "id": "list123", "name": "VIP customers" }],
    "tags": [{ "id": "tagHotLead", "name": "Hot lead" }],
    "campaigns": [{ "id": "campaign789", "name": "Spring promo" }],
    "currentCampaign": { "id": "campaign789", "name": "Spring promo" },
    "lastMessage": {
      "direction": "inbound",
      "body": "Sounds good, thanks!",
      "status": "received",
      "timestamp": "2026-06-09T10:21:00.000Z"
    }
  }
}
```

De contact-ID wordt zowel op het hoogste niveau (`contactId`) als in het object (`contact.id`) geretourneerd. Als er geen overeenkomst is, ontvang je een `404` met `{ "success": false, "message": "Contact not found" }`.

> **`avatarUrl`** is de profielfoto van de contactpersoon, afkomstig van WhatsApp of Meta wanneer zij je een bericht sturen. Deze is alleen-lezen: je kunt deze niet instellen en is `null` voor contactpersonen die geen foto hebben of die je bereiken via een kanaal dat er geen deelt. Behandel de link als tijdelijk in plaats van deze op te slaan, aangezien sommige van deze fotolinks verlopen en automatisch worden vernieuwd. (In het onderstaande lijsteindpunt wordt dezelfde waarde `avatar_url` genoemd.)

> **Telefoonnummers in URL's.** Een `+`-teken in een query-string moet URL-geëncodeerd zijn als `%2B`, anders wordt het gelezen als een spatie. De bovenstaande voorbeelden doen dit automatisch voor je.

---

## Een contactpersoon ophalen op ID

`GET /contacts/{contactId}`

Wanneer je het ID van een contactpersoon al hebt, kun je deze direct ophalen. De vorm van het antwoord is identiek aan de bovenstaande opzoekopdracht.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.contact);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["contact"])
```

Een contact-ID dat niet bestaat in je account retourneert een `404`.

---

## Contactstatistieken ophalen

`GET /contacts/{contactId}/stats`

Geeft geaggregeerde berichtstatistieken terug voor één contact: totalen, AI- versus menselijke antwoorden, verbruikte credits en tijdstempels van het eerste/laatste bericht.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.totalMessages, data.creditsUsed);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["totalMessages"], data["creditsUsed"])
```

**Antwoord**

```json
{
  "success": true,
  "totalMessages": 48,
  "sent": 21,
  "received": 27,
  "aiReplies": 18,
  "humanReplies": 3,
  "creditsUsed": 34,
  "botMessageCount": 18,
  "firstMessageAt": "2026-05-01T09:00:00.000Z",
  "lastMessageAt": "2026-06-09T10:21:00.000Z"
}
```

`botMessageCount` is dezelfde AI-berichtenteller die de "reset"-knop in de app voor een contact op nul zet. `creditsUsed` is het lopende credittotaal voor dit contact, niet alleen de cijfers van dit antwoord. Een contact-ID die niet bestaat in uw account retourneert een `404`.

---

## Contactpersonen weergeven

`GET /contacts`

Roep `GET /contacts` aan **zonder** `phoneNumber` of `email` om door al je contactpersonen te bladeren, beginnend bij de nieuwste. Elke pagina retourneert compacte samenvattingen van contactpersonen (lijsten, tags en campagnes worden geretourneerd als ID-arrays in plaats van volledige objecten) en een `next_cursor`.

| Query-parameter | Beschrijving |
|---|---|
| `limit` | Paginagrootte. Standaard 50, maximaal 100. |
| `cursor` | De `next_cursor`-waarde van de vorige pagina. Laat deze weg op de eerste pagina. |
| `listId` | Optioneel. Retourneer alleen contactpersonen die tot deze lijst behoren. |

Om elke pagina te doorlopen: doe de eerste aanroep zonder cursor en blijf vervolgens de geretourneerde `next_cursor` doorgeven als `cursor`. **Stop wanneer `next_cursor` gelijk is aan `null`** — dat betekent dat er geen resultaten meer zijn.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?limit=50&apiKey=YOUR_API_KEY"

# next page:
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?limit=50&cursor=contact_abc123&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
async function listAllContacts() {
  const all = [];
  let cursor = null;
  do {
    const url = new URL("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("cursor", cursor);
    const res = await fetch(url, { headers: { "X-API-Key": "YOUR_API_KEY" } });
    const data = await res.json();
    all.push(...data.contacts);
    cursor = data.next_cursor;
  } while (cursor);
  return all;
}
```

**Python**

```python
import requests

def list_all_contacts():
    all_contacts = []
    cursor = None
    while True:
        params = {"limit": 100}
        if cursor:
            params["cursor"] = cursor
        res = requests.get(
            "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
            headers={"X-API-Key": "YOUR_API_KEY"},
            params=params,
        )
        data = res.json()
        all_contacts.extend(data["contacts"])
        cursor = data["next_cursor"]
        if not cursor:
            break
    return all_contacts
```

**Antwoord**

```json
{
  "success": true,
  "contacts": [
    {
      "id": "contact_abc123",
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "jane@example.com",
      "phone_number": "+15551234567",
      "channel": "whatsapp",
      "is_bot_active": true,
      "is_private": false,
      "do_not_disturb": false,
      "avatar_url": "https://example.com/photo.jpg",
      "custom_fields": {},
      "created_at": "2026-06-01T09:00:00.000Z",
      "list_ids": ["list123"],
      "tag_ids": ["tagHotLead"],
      "campaign_ids": ["campaign789"],
      "current_campaign_id": "campaign789"
    }
  ],
  "next_cursor": "contact_abc123"
}
```

::: note
**Let op:** Filteren op een `listId` die niet bestaat in je account retourneert een `404`. Een ongeldige `cursor` retourneert een `400`.
:::


---

## Contacten tellen

`GET /contacts/count`

Geeft terug hoeveel contacten overeenkomen met een filter, plus een uitsplitsing per kanaal, zonder dat je er doorheen hoeft te bladeren. Dit is de juiste aanroep voor elke "hoeveel"-vraag — een dashboard-tegel, een automatisering of een vraag aan Champ. Alle filters zijn optioneel en het combineren van meerdere filters verkleint het aantal (een contact moet aan elk opgegeven filter voldoen).

| Query-parameter | Beschrijving |
|---|---|
| `agentId` | Alleen contacten toegewezen aan deze AI-agent. Gebruik `none` voor contacten zonder toegewezen agent (deze worden beantwoord door de standaardagent van het kanaal). |
| `channel` | Alleen contacten op dit kanaal, bijv. `whatsapp`, `messenger`, `instagram`, `sms`, `email`, `chat_widget`. |
| `tag` | Alleen contacten met deze tag, op basis van de **naam** van de tag (hoofdlettergevoeligheid maakt niet uit). Een tagnaam die je niet hebt, geeft een `404` terug. |
| `listId` | Alleen contacten op deze lijst. |
| `botActive` | `true` of `false` — alleen contacten waarvan de AI-assistent is ingeschakeld of uitgeschakeld. |
| `status` | Alleen contacten met deze status, bijv. `Lead`. |
| `rules` | Een URL-gecodeerd JSON-regels-object, met dezelfde vorm als een slimme lijst (zie [De `smart_rules` vorm](#the-smart_rules-shape) verderop). Kan niet worden gecombineerd met de andere filters. |

Stuur helemaal geen filter en je krijgt het totaal aantal contacten in je account.

**cURL**

```bash
# everything
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count?apiKey=YOUR_API_KEY"

# only the contacts one agent handles on Messenger
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count?agentId=agent_xyz789&channel=messenger&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const url = new URL("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count");
url.searchParams.set("agentId", "agent_xyz789");
url.searchParams.set("channel", "messenger");

const res = await fetch(url, { headers: { "X-API-Key": "YOUR_API_KEY" } });
const data = await res.json();
console.log(data.total);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"agentId": "agent_xyz789", "channel": "messenger"},
)
data = res.json()
print(data["total"])
```

**Antwoord**

```json
{
  "success": true,
  "total": 3423,
  "by_channel": { "messenger": 2744, "instagram": 667, "none": 12 },
  "filters": { "agentId": "agent_xyz789" }
}
```

`by_channel` splitst hetzelfde totaal per kanaal; contacten die niet op een kanaal staan, worden geteld onder `none`. `filters` geeft de toegepaste filters terug, zodat je kunt controleren of de aanroep deed wat je bedoelde.

::: note
**Let op:** Het verzenden van `rules` samen met een ander filter, of een `rules`-waarde die geen geldige JSON is, geeft een `400` terug. Een tagnaam of lijst-ID die niet bestaat in je account geeft een `404` terug.
:::


---

## Een contact bijwerken

`PUT /contacts/{contactId}`

Werkt een bestaand contact bij. Alleen de velden die je opneemt worden gewijzigd — laat alles weg wat je niet wilt aanpassen. Je moet ten minste één veld meesturen, anders krijg je een `400` ("Geen velden om bij te werken").

| Veld | Beschrijving |
|---|---|
| `firstName` | Voornaam. |
| `lastName` | Achternaam. |
| `email` | E-mailadres. |
| `is_bot_active` | Of de AI-assistent op dit contact reageert. |
| `is_private` | Markeren als privé. Dit instellen op `true` schakelt ook de AI-assistent uit. |
| `do_not_disturb` | Pauzeer geautomatiseerde outreach naar dit contact. Stopt ook de AI met reageren. |
| `follow_ups_disabled` | Stop alle geautomatiseerde follow-ups voor dit contact (quick, cycle en cold-lead) terwijl de AI blijft reageren op berichten die zij sturen. Handig zodra iemand iets heeft gekocht. Blijft uitgeschakeld totdat je het terugzet op `false`. |
| `lead_profile` | Vrije tekst voor leadnotities. |
| `custom_fields` | Een object met aangepaste velden. **Samengevoegd per sleutel** — alleen de sleutels die je verstuurt worden geschreven, de rest van de bestaande aangepaste velden blijft behouden. Je kunt ook aangepaste veldsleutels doorgeven op het hoogste niveau. |

**cURL**

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "firstName": "Jane", "do_not_disturb": true }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ firstName: "Jane", do_not_disturb: true }),
});
const data = await res.json();
console.log(data.message);
```

**Python**

```python
import requests

res = requests.put(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"firstName": "Jane", "do_not_disturb": True},
)
print(res.json()["message"])
```

**Antwoord**

```json
{
  "success": true,
  "message": "Contact updated successfully"
}
```

> **Aangepaste velden worden samengevoegd, niet vervangen.** Het versturen van `{ "custom_fields": { "tier": "gold" } }` stelt alleen `tier` in — alle andere aangepaste velden van het contact blijven precies zoals ze waren. Om een aangepast veld volledig te verwijderen voor alle contacten, gebruik [Een aangepast veld verwijderen](#delete-a-custom-field).

---

## Tags toevoegen of verwijderen

`POST /contacts/{contactId}/tags`

Voegt tags toe aan en/of verwijdert tags van een enkel contact in één aanroep. Geef tag-**ID's** door in `addTagIds` en `removeTagIds`. Ten minste één van de twee moet niet leeg zijn.

De tags moeten al bestaan in je account — maak ze eerst aan via het [tags-eindpunt](reference.md). Als het contact of een van de verwezen tags niet bestaat, krijg je een `404`.

| Veld | Beschrijving |
|---|---|
| `addTagIds` | Array van tag-ID's om toe te voegen aan de contactpersoon. |
| `removeTagIds` | Array van tag-ID's om te verwijderen van de contactpersoon. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "addTagIds": ["tagHotLead"], "removeTagIds": ["tagColdLead"] }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    addTagIds: ["tagHotLead"],
    removeTagIds: ["tagColdLead"],
  }),
});
const data = await res.json();
console.log(data.added, data.removed);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"addTagIds": ["tagHotLead"], "removeTagIds": ["tagColdLead"]},
)
data = res.json()
print(data["added"], data["removed"])
```

**Antwoord**

```json
{
  "success": true,
  "contact_id": "contact_abc123",
  "added": 1,
  "removed": 1
}
```

---

## Uw tagbibliotheek beheren

Deze endpoints beheren de tag zelf — het hernoemen of verwijderen ervan in uw account — in tegenstelling tot het toevoegen of verwijderen van een tag bij een contact (zie [Tags toevoegen of verwijderen](#add-or-remove-tags) hierboven). Elke tag in uw account heeft een ID (`tagId`): degene die wordt getoond in de tagmanager van uw dashboard, en degene die wordt geretourneerd als `data.tag_id` wanneer u een tag aanmaakt met `POST /tags` en een JSON-body van `{ "name": "..." }` (geen `phoneNumber`, `email` of `contactId`).

### Een tag bijwerken

`PUT /tags/{tagId}`

Verzend alleen de velden die u wijzigt.

| Veld | Beschrijving |
|---|---|
| `name` | De naam van de tag. |

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags/tagHotLead?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Hot lead (Q3)" }'
```

**Antwoord**

```json
{ "success": true, "tag_id": "tagHotLead" }
```

Een `tagId` die niet bestaat in uw account retourneert een `404`.

### Een tag verwijderen

`DELETE /tags/{tagId}`

Verwijdert één tag op ID. **Dit kan niet ongedaan worden gemaakt** — contacten met de tag verliezen deze simpelweg. Het verwijderen van een tag die al weg is (of nooit heeft bestaan) retourneert `200` met `deleted: 0` in plaats van een `404`, aangezien er niets is om op te sommen.

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags/tagColdLead?apiKey=YOUR_API_KEY"
```

**Antwoord**

```json
{ "success": true, "deleted": 1 }
```

### Meerdere tags tegelijk verwijderen

`DELETE /tags`

| Veld | Beschrijving |
|---|---|
| `tagIds` | Array van tag-ID's om te verwijderen (max. 1000). |

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tagIds": ["tagColdLead", "tagUnsubscribed"] }'
```

**Antwoord**

```json
{ "success": true, "deleted": 2 }
```

ID's die niet bestaan of bij een ander account horen, worden stilzwijgend overgeslagen en niet meegeteld in `deleted`.

---

## Bulk een vlag instellen

`POST /contacts/bulk-flag`

Stelt één booleaanse vlag in voor veel contactpersonen tegelijk. Maximaal 500 contact-ID's per verzoek. ID's die niet bestaan in uw account worden overgeslagen en meegeteld in `skipped`.

| Veld | Beschrijving |
|---|---|
| `contactIds` | Array van contact-ID's om bij te werken (max. 500). |
| `field` | Welke vlag moet worden ingesteld. Eén van `bot_active` (AI-assistent aan/uit), `dnd` (geautomatiseerde outreach pauzeren), `spam`, `private`. |
| `value` | De booleaanse waarde waarop de vlag moet worden ingesteld. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contactIds": ["contactId1", "contactId2"],
    "field": "bot_active",
    "value": false
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contactIds: ["contactId1", "contactId2"],
    field: "bot_active",
    value: false,
  }),
});
const data = await res.json();
console.log(data.updated, data.skipped);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "contactIds": ["contactId1", "contactId2"],
        "field": "bot_active",
        "value": False,
    },
)
data = res.json()
print(data["updated"], data["skipped"])
```

**Antwoord**

```json
{
  "success": true,
  "updated": 2,
  "skipped": 0
}
```

---

## Bulk contactpersonen importeren

`POST /contacts/import`

Maakt tot 500 contacten in één aanroep vanuit een JSON-array. Elk record heeft een `phone_number` in internationaal formaat nodig; al het overige is optioneel. Records met ongeldige telefoonnummers of niet-ondersteunde kanalen worden **overgeslagen** (niet aangemaakt), en elk overgeslagen record wordt gerapporteerd met de index en de reden — zodat u alleen de fouten kunt herstellen en het opnieuw kunt proberen.

Telefoonnummers die al in uw account bestaan, worden standaard overgeslagen als `duplicate`. Stuur `updateExisting: true` om die contacten in plaats daarvan te **bijwerken**: de velden in het record overschrijven die van het contact (`first_name`, `last_name`, `email`, `lead_profile` en `custom_fields` worden sleutel voor sleutel samengevoegd), `tags` worden toegevoegd en het contact wordt toegevoegd aan `listId`. Kanaal, telefoonnummer en bot-vlaggen worden nooit gewijzigd bij een bestaand contact.

U kunt optioneel elk geïmporteerd (of bijgewerkt) contact toevoegen aan een lijst met `listId`, een `defaultChannel` instellen voor records die er geen specificeren, en records labelen met `tags` (labelnamen — ontbrekende labels worden aangemaakt, bestaande labels worden hoofdletterongevoelig gematcht).

**Top-level velden**

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `contacts` | Ja | Array van contactrecords (max. 500). |
| `listId` | Nee | Lijst om elk geïmporteerd (en bijgewerkt) contact aan toe te voegen. Moet een lijst in uw account zijn. |
| `defaultChannel` | Nee | Kanaal toegepast op records die `channel` weglaten. Een van `whatsapp`, `sms`, `whatsapp_web`. Standaard `whatsapp`. |
| `updateExisting` | Nee | `true` om contacten bij te werken wiens telefoonnummer al bestaat in plaats van ze over te slaan als `duplicate`. Standaard `false`. |

**Per-record velden**

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `phone_number` | Ja | Telefoonnummer in internationaal formaat (een `+` aan het begin wordt toegevoegd indien ontbrekend). |
| `first_name` | Nee | Voornaam. |
| `last_name` | Nee | Achternaam. |
| `email` | Nee | E-mailadres. |
| `channel` | Nee | Een van `whatsapp`, `sms`, `whatsapp_web`. Valt terug op `defaultChannel`. |
| `is_bot_active` | Nee | Of de AI-assistent antwoordt. Standaard `true`. |
| `is_private` | Nee | Markeren als privé. Standaard `false`. |
| `lead_profile` | Nee | Vrije tekst voor leadnotities. |
| `custom_fields` | Nee | Object van aangepaste veldsleutels en waarden. |
| `tags` | Nee | Array van labelnamen (een enkele `"a; b"` string werkt ook). Labels die niet bestaan worden aangemaakt; bestaande worden gematcht zonder hoofdlettergevoeligheid. Max. 25 per record. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      { "phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee", "tags": ["vip", "newsletter"] },
      { "phone_number": "+12025551235", "first_name": "Bob" }
    ],
    "listId": "list123",
    "defaultChannel": "whatsapp_web",
    "updateExisting": true
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contacts: [
      { phone_number: "+12025551234", first_name: "Ann", last_name: "Lee", tags: ["vip", "newsletter"] },
      { phone_number: "+12025551235", first_name: "Bob" },
    ],
    listId: "list123",
    defaultChannel: "whatsapp_web",
    updateExisting: true,
  }),
});
const data = await res.json();
console.log(`Imported ${data.imported}, updated ${data.updated}, skipped ${data.skipped.length}`);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "contacts": [
            {"phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee", "tags": ["vip", "newsletter"]},
            {"phone_number": "+12025551235", "first_name": "Bob"},
        ],
        "listId": "list123",
        "defaultChannel": "whatsapp_web",
        "updateExisting": True,
    },
)
data = res.json()
print(f"Imported {data['imported']}, updated {data['updated']}, skipped {len(data['skipped'])}")
```

**Antwoord**

```json
{
  "success": true,
  "imported": 2,
  "contact_ids": ["contact_abc123", "contact_def456"],
  "updated": 0,
  "updated_contact_ids": [],
  "skipped": []
}
```

Als sommige records niet kunnen worden aangemaakt, verschijnen ze in `skipped` met de reden (hier zonder `updateExisting`, dus het bestaande nummer wordt overgeslagen):

```json
{
  "success": true,
  "imported": 1,
  "contact_ids": ["contact_abc123"],
  "updated": 0,
  "updated_contact_ids": [],
  "skipped": [
    { "index": 1, "phone_number": "+12025551235", "reason": "duplicate" }
  ]
}
```

Met `updateExisting: true` rapporteert hetzelfde verzoek het bestaande contact onder `updated` / `updated_contact_ids` in plaats daarvan.

Mogelijke redenen voor overslaan: `invalid_record`, `missing_phone_number`, `invalid_phone_number`, `invalid_channel`, `duplicate_in_request`, `duplicate`, `contact_limit_reached`, `create_failed`.

> **Abonnementslimieten.** Als de contactlimiet van uw abonnement dit aantal nieuwe contacten niet toestaat, wordt het volledige verzoek vooraf afgewezen met een `403`. Als de limiet halverwege wordt bereikt, worden de resterende records geretourneerd als overgeslagen met reden `contact_limit_reached`.

---

## Contacten importeren vanuit een CSV-bestand

Voor imports die groter zijn dan wat [bulk import](#bulk-import-contacts) ondersteunt (tot ongeveer 50.000 rijen), kun je een asynchrone importtaak in de wachtrij plaatsen voor een CSV-bestand dat al in de opslag van je account staat, en vervolgens de status opvragen totdat deze is voltooid.

### De import starten

`POST /contacts/import-csv`

| Veld | Vereist | Beschrijving |
|---|---|---|
| `csvStoragePath` | Ja | Opslagpad van het CSV-bestand, onder `users/{your account id}/imports/`, eindigend op `.csv`. |
| `listName` | Ja | Maakt (of hergebruikt) een lijst met deze naam en voegt elk geïmporteerd contact hieraan toe. |
| `existingListRefs` | Nee | Array van bestaande lijst-ID's om elk geïmporteerd contact ook aan toe te voegen. |
| `defaultChannel` | Nee | Kanaal dat wordt toegepast op rijen waarvoor er geen is opgegeven. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csvStoragePath": "users/abc123/imports/leads.csv",
    "listName": "Webinar signups"
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    csvStoragePath: "users/abc123/imports/leads.csv",
    listName: "Webinar signups",
  }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "csvStoragePath": "users/abc123/imports/leads.csv",
        "listName": "Webinar signups",
    },
)
job_id = res.json()["job_id"]
```

**Antwoord** (`202` — de import staat in de wachtrij, maar is nog niet voltooid)

```json
{
  "success": true,
  "job_id": "csvimp_abc123",
  "status": "queued"
}
```

> **Het bestand in de opslag krijgen.** Dit eindpunt start en volgt de importtaak; het accepteert zelf geen upload. Het CSV-bestand moet al op `csvStoragePath` staan voordat je dit aanroept — de CSV-importfunctie van het dashboard doet dit als eerste stap.

### De importtaak pollen

`GET /contacts/import-csv/{jobId}`

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv/csvimp_abc123?apiKey=YOUR_API_KEY"
```

**Antwoord**

```json
{
  "success": true,
  "job_id": "csvimp_abc123",
  "status": "completed",
  "imported": 812,
  "updated": 0,
  "skipped": 14,
  "errors": [],
  "error_message": null
}
```

`status` doorloopt `queued` → `processing` → `completed`, of `failed` met de reden in `error_message`. Een `jobId` die niet bestaat in je account retourneert een `404`.

---

## Contacten exporteren

Start een asynchrone CSV-export van je contacten en retourneert een taak die je kunt opvragen voor voltooiing.

### Start de export

`POST /contacts/export`

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `listId` | Nee | Exporteer alleen contacten die tot deze lijst behoren. |
| `contactIds` | Nee | Exporteer alleen deze specifieke contact-ID's. |

Als je beide weglaat, worden alle contacten in je account geëxporteerd.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "listId": "list123" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ listId: "list123" }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"listId": "list123"},
)
job_id = res.json()["job_id"]
```

**Antwoord** (`202` — de export staat in de wachtrij)

```json
{
  "success": true,
  "job_id": "export_abc123",
  "status": "queued"
}
```

### De exporttaak opvragen

`GET /contacts/export/{jobId}`

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export/export_abc123?apiKey=YOUR_API_KEY"
```

**Antwoord**

```json
{
  "success": true,
  "job_id": "export_abc123",
  "status": "completed",
  "export_id": "exp_xyz789",
  "contact_count": 812,
  "error_message": null
}
```

> Zodra `status` `"completed"` is, ontvang je `export_id` en `contact_count`. Het downloaden van het gegenereerde CSV-bestand gebeurt via de pagina Export van je dashboard.

---

## Stuur een bericht naar een contact

`POST /contacts/{contactId}/send-message`

Verstuurt een bericht naar een bestaande contactpersoon via het kanaal dat deze al gebruikt. Het bericht wordt in de wachtrij geplaatst en op de achtergrond afgeleverd — het antwoord bevestigt dat het is geaccepteerd, niet dat het al is afgeleverd.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `body` | Ja | De tekst van het te verzenden bericht. |
| `mediaUrl` | Nee | URL van een mediabestand om bij te voegen. |
| `mediaContentType` | Nee | MIME-type van de bijgevoegde media (bijv. `image/jpeg`). |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Hi! Your appointment is confirmed." }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ body: "Hi! Your appointment is confirmed." }),
});
const data = await res.json();
console.log(data.messageId);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"body": "Hi! Your appointment is confirmed."},
)
print(res.json()["messageId"])
```

**Antwoord**

```json
{
  "success": true,
  "messageId": "aB3dE5fG7hI9jK1lM2nO",
  "contactId": "contact_abc123",
  "channel": "whatsapp",
  "message": "Message created successfully. Delivery is being processed."
}
```

> **Kun je nu niet verzenden?** Als de contactpersoon 'niet storen' of de privémodus heeft ingeschakeld, of zich niet op een kanaal bevindt dat uitgaande berichten kan ontvangen, wordt het verzoek afgewezen met een `422` en een verklarende `error`.

Voor verzending via telefoonnummer, Instagram-ID of een andere kanaalidentiteit in plaats van een contact-ID — en voor meer informatie over berichten in het algemeen — zie de [Messages API](messages.md).

---

## Een AI-agent toewijzen aan een contactpersoon

`POST /contacts/{contactId}/assign-agent`

Verplaatst een bestaand gesprek naar een andere AI-agent, vanaf het volgende bericht. Dit is hetzelfde als **AI-agent toewijzen** in het menu van een chat, en dezelfde stap die de actie **AI-agent of campagne toewijzen** in Automatiseringen gebruikt.

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `agentId` | Ja | De ID van de AI-agent die het moet overnemen, of `null` om de toewijzing te wissen zodat het gesprek teruggaat naar je team-inbox. |
| `triggerAIResponse` | Nee | `true` zorgt ervoor dat de nieuw toegewezen agent direct reageert op de laatste onbeantwoorde berichten van de contactpersoon. Standaard ingesteld op `false`. |

> **Wees voorzichtig met `triggerAIResponse: true`** — het stuurt het contact direct een bericht, dus gebruik het alleen wanneer je wilt dat ze nu een bericht ontvangen. Op Messenger en Instagram mislukt dat bericht als het contact meer dan 24 uur geleden voor het laatst contact met je heeft opgenomen.

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "agent_xyz789" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ agentId: "agent_xyz789" }),
});
const data = await res.json();
console.log(data.data.agentId);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"agentId": "agent_xyz789"},
)
print(res.json()["data"]["agentId"])
```

**Antwoord**

```json
{
  "success": true,
  "data": {
    "contactId": "contact_abc123",
    "agentId": "agent_xyz789",
    "aiResponseTriggered": false
  }
}
```

> De agent moet tot hetzelfde account behoren als de contactpersoon; anders wordt het verzoek afgewezen met een `404` of `403`. Vind agent-ID's op de pagina AI-agents (de URL van elke agent eindigt op zijn ID).

---

## Een AI-agent toewijzen aan vele contacten

`POST /contacts/bulk-assign-agent`

Verplaatst vele gesprekken naar een andere AI-agent in één aanroep — of wist de toewijzing voor allemaal met `null`. Het is puur een routeringswijziging: **er wordt geen bericht verzonden en de agent antwoordt niemand**. Elk contact krijgt simpelweg de nieuwe agent de volgende keer dat ze schrijven. (Daarom is er hier geen `triggerAIResponse`.)

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `agentId` | Ja | De AI-agent die het moet overnemen, of `null` om de toewijzing te wissen. |
| `contactIds` | Eén van de drie | Maximaal 500 contact-ID's om te verplaatsen. |
| `filter` | Eén van de drie | Kies de contacten op de server in plaats van ze op te sommen, nieuwste eerst. Gebruikt dezelfde sleutels als de filters van het tel-eindpunt: `agentId` (of `none`), `channel`, `tag`, `listId`, `botActive`, `status`. |
| `rules` | Eén van de drie | Een regels-object voor een slimme lijst — zie [De `smart_rules` vorm](#the-smart_rules-shape). |
| `limit` | Nee | Hoeveel contacten er in deze aanroep moeten worden verplaatst wanneer je selecteert met `filter` of `rules`. 1 tot 500, standaard is 500. |

Verzend precies één van `contactIds`, `filter` of `rules`.

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agent_xyz789",
    "filter": { "agentId": "agent_abc123", "channel": "messenger" }
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    agentId: "agent_xyz789",
    filter: { agentId: "agent_abc123", channel: "messenger" },
  }),
});
const data = await res.json();
console.log(data.updated, data.remaining);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "agentId": "agent_xyz789",
        "filter": {"agentId": "agent_abc123", "channel": "messenger"},
    },
)
data = res.json()
print(data["updated"], data["remaining"])
```

**Antwoord**

```json
{
  "success": true,
  "agentId": "agent_xyz789",
  "matched": 3415,
  "updated": 500,
  "skipped": 0,
  "remaining": 2915,
  "filters": { "agentId": "agent_abc123" }
}
```

`matched` is hoeveel contacten de selectie in totaal heeft gevonden, `updated` hoeveel er door deze aanroep zijn verplaatst, `skipped` hoeveel van de ID's die je hebt verzonden niet zijn gevonden in je account, en `remaining` hoeveel er nu nog overeenkomen nu deze aanroep is voltooid.

**Iedereen verplaatsen.** Omdat een aanroep maximaal 500 contacten verplaatst, kost een grote groep een paar aanroepen. Gebruik een filter dat niet langer overeenkomt met een contact zodra het is verplaatst — bijvoorbeeld `filter: { "agentId": "agent_abc123" }` tijdens het toewijzen aan `agent_xyz789` — en herhaal exact dezelfde aanroep totdat `remaining` terugkomt als `0`. Wanneer je in plaats daarvan `contactIds` doorgeeft, is `remaining` altijd `0`.

---

## Een contact toewijzen aan een afdeling

`POST /contacts/{contactId}/department`

"Wijs deze lead toe aan Sales" — plaatst een contact onder een benoemde afdeling en wijst deze standaard toe aan degene op die afdeling die momenteel de minste contacten heeft. Dit staat los van het [toewijzen van een AI-agent](#assign-an-ai-agent-to-a-contact): een afdeling beantwoordt "welk team is hiervan de eigenaar," een agent beantwoordt "welke AI beantwoordt dit," en het instellen van de een wist de ander nooit.

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `department_id` | Ja | De afdeling waaronder het contact wordt opgeslagen. Geef `null` door om dit te wissen. |
| `hand_to_member` | Nee | Wijs het contact ook toe aan de persoon op die afdeling met de minste werklast. Standaard `true`. Wijst nooit een contact opnieuw toe dat al eigendom is van iemand anders. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "department_id": "dept_sales" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ department_id: "dept_sales" }),
});
const data = await res.json();
console.log(data.assigned_to);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"department_id": "dept_sales"},
)
print(res.json()["assigned_to"])
```

**Antwoord**

```json
{
  "success": true,
  "department_id": "dept_sales",
  "assigned_to": "member_uid_123"
}
```

`assigned_to` is `null` wanneer het contact al eigendom was van iemand, of als je `hand_to_member: false` hebt doorgegeven.

---

## Een contact koppelen via kanalen

"Ga verder op WhatsApp" (of sms) zoekt of maakt het contact van deze persoon aan op een ander telefoon-gebaseerd kanaal en koppelt de twee aan elkaar, zodat de rest van de app hen als dezelfde persoon herkent.

### Koppelen aan een ander kanaal

`POST /contacts/{contactId}/link-channel`

| Veld | Vereist | Beschrijving |
|---|---|---|
| `channel` | Ja | Het kanaal om aan te koppelen. Een van `whatsapp`, `whatsapp_web`, `sms`. |
| `phoneNumber` | Nee | Telefoonnummer om te gebruiken op het nieuwe kanaal. Standaard wordt het eigen nummer van het broncontact gebruikt. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/link-channel?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "sms" }'
```

**Antwoord**

```json
{
  "success": true,
  "data": {
    "contact_id": "contact_def456",
    "person_id": "person_xyz789",
    "created": true
  }
}
```

`created` geeft aan of er een nieuw contact is aangemaakt voor het doel-kanaal of dat er een bestaand contact is gevonden en gekoppeld. Een tweede keer aanroepen is veilig — het retourneert hetzelfde `contact_id` met `created: false` in plaats van een duplicaat aan te maken.

Een `422` betekent dat het account deze koppeling momenteel niet kan uitvoeren: het contact bevindt zich al in die kanaalfamilie, heeft geen telefoonnummer om te gebruiken, of er is geen verbonden afzender voor het doelkanaal. Een `409` betekent dat de twee contacten al aan twee verschillende personen zijn gekoppeld — ontkoppel er eerst een.

### De gekoppelde gesprekken van een contact weergeven

`GET /contacts/{contactId}/linked`

Retourneert de andere gesprekken die dezelfde persoon zijn als dit contact. Een niet-gekoppeld contact retourneert een lege array, geen `404` — "deze persoon heeft geen andere kanalen" is een normale status.

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/linked?apiKey=YOUR_API_KEY"
```

**Antwoord**

```json
{
  "success": true,
  "data": [
    {
      "contact_id": "contact_def456",
      "channel": "sms",
      "custom_channel": null,
      "first_name": "Jane",
      "last_name": "Smith",
      "phone_number": "+15551234567",
      "last_message": "Sounds good, thanks!",
      "last_message_timestamp": "2026-06-09T10:21:00.000Z",
      "linked_from": {
        "contact_id": "contact_abc123",
        "channel": "whatsapp",
        "linked_at": "2026-06-01T09:00:00.000Z",
        "reason": "continue_on_channel"
      }
    }
  ]
}
```

### Een contact ontkoppelen

`DELETE /contacts/{contactId}/link`

Verwijdert dit contact eenzijdig van zijn persoon — alle andere contacten die nog aan die persoon zijn gekoppeld, behouden hun koppeling, dus het ontkoppelen van één van de drie heft de groep niet op.

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/link?apiKey=YOUR_API_KEY"
```

**Antwoord**

```json
{ "success": true }
```

---

## De profielfoto van een contact ophalen

`POST /contacts/{contactId}/profile-pic`

Haalt (en cachet) de WhatsApp- of Meta-profielfoto van het contact op aanvraag — dezelfde foto die wordt geretourneerd als `avatarUrl` bij [Een contact ophalen](#get-a-contact-by-phone-or-email), ververst.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/profile-pic?apiKey=YOUR_API_KEY"
```

**Antwoord**

```json
{
  "success": true,
  "avatar_url": "https://example.com/photo.jpg",
  "cached": false
}
```

`cached: true` betekent dat de URL afkomstig is van een recente ophaalactie in plaats van een nieuwe provider-opzoeking — foto's worden 7 dagen gecachet, en een contact waarvan de provider meldt dat er geen bereikbare foto is, wordt 24 uur lang als niet beschikbaar gecachet. Wanneer er geen foto is om op te halen, wordt `avatar_url` weggelaten en legt `message` uit waarom.

---

## Contacten automatisch taggen met AI

Voert de tagregels van uw account uit over de volledige gespreksgeschiedenis van een of meer contacten en past tags toe (of verwijdert ze) precies zoals de realtime tagging die tijdens een livechat plaatsvindt — dezelfde regels, dezelfde creditkosten per tag.

### Een uitvoering starten

`POST /contacts/auto-tag`

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `scope` | Ja | `"contacts"` om specifieke contacten te taggen, of `"agent"` om elk gesprek te taggen dat momenteel door één AI-agent wordt afgehandeld. |
| `contact_ids` | Verplicht wanneer `scope` `"contacts"` is | Array van contact-ID's, 1 tot 500. |
| `agent_id` | Verplicht wanneer `scope` `"agent"` is | De AI-agent wiens gesprekken moeten worden getagd. Wanneer `scope` `"contacts"` is, is dit optioneel en beperkt het alleen welke tagregels van de agent worden uitgevoerd. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/auto-tag?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "contacts", "contact_ids": ["contact_abc123", "contact_def456"] }'
```

Een **enkel** contact wordt inline uitgevoerd en retourneert het resultaat direct:

```json
{ "success": true, "result": { "tags_applied": 2, "tags_removed": 0 } }
```

**Twee of meer** contacten (of `scope: "agent"`) worden uitgevoerd als een achtergrondtaak en retourneren direct `202`:

```json
{ "success": true, "run_id": "m1x2y3-a1b2c3d4", "total": 214 }
```

### Een uitvoering pollen

`GET /contacts/auto-tag/run`

Retourneert de huidige (of meest recente) uitvoering van het account, zodat u de voortgang kunt pollen zonder zelf `run_id` bij te houden.

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/auto-tag/run?apiKey=YOUR_API_KEY"
```

**Antwoord**

```json
{
  "success": true,
  "run": {
    "run_id": "m1x2y3-a1b2c3d4",
    "status": "running",
    "total": 214,
    "processed": 58,
    "tagged_contacts": 12,
    "tags_applied": 15,
    "tags_removed": 2,
    "credits_charged": 15
  }
}
```

`run` is `null` wanneer het account er nog nooit een heeft gestart. `status` gaat van `"running"` naar `"completed"` of `"failed"`.

Er kan per account slechts één bulkuitvoering tegelijkertijd actief zijn — het starten van een tweede terwijl er al een draait, retourneert `409` met `error_code: "auto_tag_run_in_progress"`. Als de credits opraken bij een uitvoering voor één contact, wordt `402` met `error_code: "insufficient_credits"` geretourneerd; een bulkuitvoering stopt in plaats daarvan voortijdig en rapporteert in `run` hoe ver deze is gekomen.

---

## Een contact verwijderen

`DELETE /contacts/{contactId}`

Verwijdert permanent één contact op basis van ID, samen met de berichtgeschiedenis. **Dit kan niet ongedaan worden gemaakt.** Gebruik [Contacten verwijderen](#delete-contacts) hieronder om meerdere contacten in één aanroep te verwijderen.

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.success);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
```

**Antwoord**

```json
{
  "success": true
}
```

Een contact-ID die niet bestaat in uw account, of die bij een ander account hoort, retourneert een `404`.

---

## Contactpersonen verwijderen

`DELETE /contacts`

Verwijdert permanent een of meer contactpersonen op ID in één aanroep (maximaal 500 ID's). ID's die niet in je account bestaan, worden overgeslagen en meegeteld in `skipped`. **Dit kan niet ongedaan worden gemaakt.**

| Veld | Beschrijving |
|---|---|
| `contactIds` | Array van contact-ID's om te verwijderen (max. 500). |

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["contactId1", "contactId2"] }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts", {
  method: "DELETE",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ contactIds: ["contactId1", "contactId2"] }),
});
const data = await res.json();
console.log(`Deleted ${data.deleted}, skipped ${data.skipped}`);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"contactIds": ["contactId1", "contactId2"]},
)
data = res.json()
print(f"Deleted {data['deleted']}, skipped {data['skipped']}")
```

**Antwoord**

```json
{
  "success": true,
  "deleted": 2,
  "skipped": 0
}
```

---

## Een aangepast veld verwijderen

`DELETE /contacts/custom-fields/{fieldKey}`

Verwijdert één aangepaste veldsleutel van **elke** contactpersoon in uw account. Gebruik dit om op te schonen na het hernoemen of verwijderen van een aangepast veld. De sleutel mag alleen letters, cijfers, underscores en koppeltekens bevatten. Geeft aan hoeveel contactpersonen zijn bijgewerkt. **Dit kan niet ongedaan worden gemaakt.**

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(`Removed from ${data.updated} contacts`);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(f"Removed from {res.json()['updated']} contacts")
```

**Antwoord**

```json
{
  "success": true,
  "updated": 42
}
```

::: note
**Let op:** Een veldsleutel met niet-ondersteunde tekens retourneert een `400`.
:::


---

## Lijsten

Lijsten groeperen contacten. Een lijst is ofwel **statisch** (jij bepaalt wie erin staat) of **slim** (het lidmaatschap wordt berekend op basis van regels en automatisch up-to-date gehouden — zie [Lijsten & Contacten organiseren](../get-started/list-and-contact-management.md#smart-lists-auto-updating)).

| Veld | Beschrijving |
|---|---|
| `name` | Vereist bij aanmaak. Maximaal 100 tekens. |
| `status` | `live` (standaard) of `draft`. Kleine letters. |
| `contact_ids` | Array met contact-ID's om aan de lijst toe te voegen. **Alleen statische lijsten.** |
| `type` | `static` (standaard) of `smart`. |
| `smart_rules` | De regelset — vereist wanneer `type` gelijk is aan `smart`. Zie hieronder. |

### Een lijst aanmaken

`POST /lists`

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Hot leads (active)",
        "type": "smart",
        "smart_rules": {
          "match": "all",
          "conditions": [
            { "field": "tags", "op": "has_any", "value": ["tagHotLead"] },
            { "field": "last_activity_at", "op": "within_last", "value": { "amount": 90, "unit": "days" } }
          ]
        }
      }'
```

**Antwoord**

```json
{
  "success": true,
  "list_id": "list_abc123",
  "evaluation": { "added": 3, "removed": 0, "total": 3 }
}
```

Een slimme lijst wordt **inline** geëvalueerd, in hetzelfde verzoek, dus `evaluation` vertelt je precies wie er uiteindelijk in terecht is gekomen. Bij een statische lijst is `evaluation` gelijk aan `null`.

### Een lijst bijwerken

`PUT /lists/{listId}`

Verstuur alleen de velden die je wijzigt. Het wijzigen van `smart_rules` zorgt ervoor dat de lijst onmiddellijk opnieuw wordt geëvalueerd en hetzelfde `evaluation` object wordt geretourneerd.

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists/list_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "smart_rules": { "match": "any", "conditions": [ { "field": "tags", "op": "has_any", "value": ["tagHotLead", "tagWebinar"] } ] } }'
```

Je kunt een lijst tussen de twee soorten schakelen:

- **Statisch → slim**: stuur `{ "type": "smart", "smart_rules": { … } }`. De regels worden direct toegepast.
- **Slim → statisch**: stuur `{ "type": "static" }`. De regels worden verwijderd en iedereen die op de lijst staat, blijft erop staan.

### De `smart_rules` vorm

```json
{
  "match": "all",
  "conditions": [
    { "field": "tags", "op": "has_any", "value": ["tagHotLead"] },
    { "field": "channel", "op": "is_any", "value": ["whatsapp", "sms"] },
    { "field": "last_incoming_message_at", "op": "not_within_last", "value": { "amount": 7, "unit": "days" } },
    { "field": "created_at", "op": "after", "value": "2026-01-01" },
    { "field": "is_bot_active", "op": "is", "value": true },
    { "field": "email", "op": "is_set" },
    { "field": "custom_field", "key": "Plan", "op": "eq", "value": "pro" }
  ]
}
```

- `match` — `all` (elke voorwaarde moet waar zijn) of `any` (ten minste één).
- `conditions` — 1 tot 20 voorwaarden, elk maximaal 100 waarden, strings tot 200 tekens.

| `field` | `op` | `value` |
|---|---|---|
| `tags` | `has_any`, `has_all`, `has_none` | array van tag-ID's |
| `lists` | `in_any`, `not_in_any` | array van lijst-ID's (**alleen statische lijsten** — een slimme lijst kan niet worden opgebouwd uit een andere slimme lijst) |
| `channel` | `is_any`, `is_none` | array van kanalen |
| `status` | `is_any`, `is_none` | array van contactstatussen |
| `created_at`, `last_activity_at`, `last_incoming_message_at`, `last_outgoing_message_at`, `first_ai_interaction_at`, `last_ai_interaction_at` | `within_last`, `not_within_last` | `{ "amount": 1–3650, "unit": "hours" \| "days" }` |
| dezelfde datumvelden | `before`, `after` | ISO-datum (`"2026-01-01"`, vergeleken als hele dagen) of volledige ISO-datum/tijd (`"2026-01-01T14:30:00Z"`, vergeleken tot op het exacte moment) |
| dezelfde datumvelden | `is_set`, `not_set` | — |
| `has_interacted_with_ai` | `is` | `true` / `false` — `true` komt overeen met contacten die de AI ten minste één keer (ooit) een bericht heeft gestuurd |
| `is_bot_active`, `do_not_disturb`, `is_private`, `has_ever_responded` | `is` | `true` / `false` |
| `email`, `phone_number`, `first_name`, `last_name` | `is_set`, `not_set`, `contains`, `not_contains` | string voor de `contains` formulieren |
| `current_campaign_id`, `assigned_agent` | `is_any`, `is_none`, `is_set`, `not_set` | array van ID's voor de `is_any` / `is_none` formulieren |
| `custom_field` (plus een `key`) | `eq`, `neq`, `contains`, `not_contains`, `is_set`, `not_set` | string voor de waarde-formulieren |

`not_within_last` komt ook overeen met contacten waarvoor de datum nooit is ingesteld ("meer dan N geleden, **of nooit**"), en tekstvergelijkingen negeren hoofdlettergebruik.

**AI-betrokkenheid.** `has_interacted_with_ai` is de lifetime-vlag: `true` voor elk contact waarnaar uw AI ten minste één bericht heeft verzonden, `false` voor alle anderen (inclusief contacten die alleen door uw team zijn beantwoord). Deze wordt gestempeld bij het eerste bericht van de AI aan een contact en wordt nooit gewist, dus het uitschakelen van AI-antwoorden voor het contact of het verplaatsen naar een andere campagne reset dit niet. Voor een *periode* — "de contacten die mijn AI deze maand heeft afgehandeld", de gebruikelijke facturatievraag — gebruikt u in plaats daarvan het bereik `last_ai_interaction_at`:

```json
{ "field": "last_ai_interaction_at", "op": "within_last", "value": { "amount": 30, "unit": "days" } }
```

Verwar beide niet met `is_bot_active` (de AI *mag* antwoorden, niet dat hij dat heeft gedaan) of `has_ever_responded` (het *contact* schreef terug, aan wie dan ook). Dezelfde twee stempels worden bij elk contact geretourneerd als `first_ai_interaction_at` / `last_ai_interaction_at`, en de gehele regelset werkt ook op `GET /contacts?rules=`, zodat u overeenkomsten kunt tellen zonder een lijst aan te maken.

### Een regelset bekijken

`POST /lists/preview`

Telt en toont voorbeelden van de contacten die een regelset zou matchen, zonder iets aan te maken of te wijzigen. Gebruik dit om regels te controleren voordat je ze opslaat.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists/preview?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "smart_rules": { "match": "all", "conditions": [ { "field": "tags", "op": "has_any", "value": ["tagHotLead"] } ] } }'
```

**Antwoord**

```json
{
  "success": true,
  "count": 3,
  "sample": [
    {
      "id": "contact_abc123",
      "first_name": "Sofia",
      "last_name": "Martinez",
      "phone_number": "+31600000000",
      "email": "sofia@example.com",
      "channel": "whatsapp"
    }
  ]
}
```

`sample` bevat maximaal 10 contacten, gesorteerd op meest recent actief.

### Een slimme lijst nu opnieuw uitvoeren

`POST /lists/{listId}/evaluate`

Forceert een onmiddellijke herevaluatie (hetzelfde als wat **Nu vernieuwen** doet in het dashboard). Slimme lijsten worden al bijgewerkt wanneer een contact wijzigt, en elke 15 minuten voor op tijd gebaseerde regels, dus dit is alleen nodig wanneer je het resultaat *nu meteen* wilt hebben.

**Antwoord**

```json
{
  "success": true,
  "list_id": "list_abc123",
  "evaluation": { "added": 2, "removed": 1, "total": 4 }
}
```

`evaluation.skipped: true` betekent dat er al een andere evaluatie van dezelfde lijst bezig was en deze aanroep niets heeft gedaan.

### Slimme lijsten accepteren geen handmatig toegevoegde leden

Lidmaatschaps-endpoints retourneren **`409`** met `"This is a smart list — its members are computed from its rules. Edit the rules instead."` wanneer de doellijst slim is. Dit geldt voor `POST /contacts/lists`, `DELETE /contacts/lists`, `POST /contacts/lists/batch`, `contact_ids` op `POST /lists` en `PUT /lists/{listId}`, en het kiezen van een slimme lijst als doel voor een CSV-import. Wijzig in plaats daarvan de regels.

Het aanroepen van `POST /lists/{listId}/evaluate` op een **statische** lijst is ook een `409` — deze heeft geen regels om uit te voeren.

---

## Contacts API-fouten

Contact-endpoints retourneren de standaard fouten-envelop:

```json
{
  "success": false,
  "error": "Contact not found"
}
```

Sommige endpoints bevatten ook `error_code`, wat meestal overeenkomt met de HTTP-status — de enige uitzondering is het geval van een dubbel contact hieronder, waarbij de HTTP-status `200` is en alleen `error_code` de `409` bevat. De codes die specifiek zijn voor contact-endpoints:

| Code | Wanneer dit gebeurt op een contact-eindpunt |
|---|---|
| `400` | Ongeldig verzoek — een ontbrekend/ongeldig veld, lege body, ongeldige cursor, of meer dan 500 ID's in een batch. |
| `402` | Onvoldoende credits om een AI-tagging-run op één contact te voltooien (`error_code: "insufficient_credits"`). |
| `404` | Het contact, de lijst of de tag is niet gevonden in uw account. |
| `409` | Er bestaat al een contact met dat telefoonnummer (bij aanmaken). Geretourneerd als `error_code` in de body met een HTTP-status van `200`, dus vertak hier op `error_code`. Ook geretourneerd wanneer een bulk auto-tag-run al bezig is (`error_code: "auto_tag_run_in_progress"`), of wanneer het koppelen van een contact aan een ander kanaal twee contacten zou samenvoegen die al aan twee verschillende personen zijn gekoppeld. |
| `422` | Het contact kan momenteel geen bericht ontvangen (niet storen, privé, of niet-ondersteund kanaal). Op het kanaalkoppelings-eindpunt dekt dit ook het ontbreken van een telefoonnummer, een niet-ondersteunde kanaalkoppeling, of het ontbreken van een verbonden afzender voor het doelkanaal. |

Een `403` op een contact-endpoint kan ook duiden op een probleem met de contactlimiet of lijsttoestemming in plaats van toegang via het abonnement. De gedeelde codes die elk endpoint kan retourneren — `401`, `403` (uw abonnement bevat geen API-toegang), `429` (snelheidslimiet) en `500` — staan vermeld met richtlijnen voor opnieuw proberen in [Fouten & Paginering](errors-and-pagination.md).

---

## Volgende stappen

- [Messages API](messages.md) — berichten verzenden op basis van kanaalidentiteit en gesprekken beheren.
- [API-referentie](reference.md) — volledige lijst met endpoints, inclusief tags en lijsten.
- [API-toegang](../integrations/api-access.md) — authenticatie, snelheidslimieten en foutafhandeling.
