
# Kontakte-API

Ein Kontakt ist eine einzelne Person, der Sie Nachrichten senden – mit Name, Telefonnummer, E-Mail, Kanal, Tags, benutzerdefinierten Feldern sowie den Listen und Kampagnen, denen sie angehört. Mit der Kontakte-API können Sie Kontakte erstellen, suchen, aktualisieren, mit Tags versehen, in großen Mengen importieren und entfernen, ohne das Dashboard verwenden zu müssen.

Alle Pfade auf dieser Seite sind relativ zur Basis-URL:

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

Das bedeutet, `/contacts` entspricht `https://api.youraiconnector.com/v1/contacts`.

> **Neu bei der API?** Lesen Sie zuerst [API-Zugriff](../integrations/api-access.md) – dort wird erklärt, wie Sie Ihren API-Schlüssel generieren, welche drei Authentifizierungsmethoden es gibt, wie Ratenbegrenzungen funktionieren und wie das Fehlerformat aussieht. Alles auf dieser Seite setzt voraus, dass Sie bereits über einen funktionierenden API-Schlüssel verfügen.

---

## Über Kontakt-IDs

Jeder Kontakt hat eine eindeutige ID. Die ID, die Sie beim **Erstellen** eines Kontakts (in `data.contactId`) erhalten, ist dieselbe ID, die Sie überall sonst verwenden – um diesen Kontakt abzurufen, zu aktualisieren, mit Tags zu versehen, eine Nachricht zu senden oder ihn zu löschen. Speichern Sie sie einmal und verwenden Sie sie wieder.

Sie müssen einen Kontakt nicht erstellen, um seine ID zu erhalten. Sie können auch nach Telefonnummer oder E-Mail suchen (siehe [Kontakt abrufen](#get-a-contact-by-phone-or-email)) oder alle Ihre Kontakte durchblättern (siehe [Kontakte auflisten](#list-contacts)). Jede dieser Methoden gibt dieselbe ID zurück.

---

## Kontakt erstellen

`POST /contacts`

Fügt Ihrem Konto einen neuen Kontakt hinzu. Eine **Telefonnummer mit Ländervorwahl ist erforderlich** – eine E-Mail-Adresse allein reicht nicht aus. Alles andere ist optional.

Sie können den neuen Kontakt optional direkt mit `listId` (eine einzelne Liste) oder `listIds` (ein Array) in eine oder mehrere Listen aufnehmen. Wenn beides gesendet wird, hat `listIds` Vorrang.

Jedes Feld, das Sie senden und das nicht eines der Standard-Erstellungsfelder in der unten stehenden Tabelle **Kontakt erstellen** ist (`phoneNumber`, `firstName`, `lastName`, `email`, `channel`, `is_bot_active`, `is_private`, `lead_profile`, `listId`, `listIds`, `custom_fields`), wird automatisch als **benutzerdefiniertes Feld** gespeichert — daher funktioniert eine flache Payload von einem Tool wie Make oder Zapier ohne Verschachtelung. Sie können auch ein explizites `custom_fields`-Objekt übergeben.

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `phoneNumber` | Ja | Die Telefonnummer des Kontakts mit Ländervorwahl (z. B. `+15551234567`). |
| `firstName` | Nein | Vorname. |
| `lastName` | Nein | Nachname. |
| `email` | Nein | E-Mail-Adresse. |
| `channel` | Nein | Nachrichtenkanal. Einer der Werte `whatsapp`, `sms`, `whatsapp_web`. Standardwert ist `whatsapp`. |
| `is_bot_active` | Nein | Ob der KI-Assistent auf diesen Kontakt antwortet. Standardwert ist `true`. |
| `is_private` | Nein | Markiert den Kontakt als privat. Wenn `true`, ist der KI-Assistent für diesen Kontakt deaktiviert. Standardwert ist `false`. |
| `lead_profile` | Nein | Freitext-Notizen über den Lead. |
| `listId` | Nein | Eine einzelne Listen-ID, der der Kontakt hinzugefügt werden soll. |
| `listIds` | Nein | Ein Array von Listen-IDs, denen der Kontakt hinzugefügt werden soll (hat Vorrang vor `listId`). |
| `custom_fields` | Nein | Ein Objekt mit Ihren eigenen Schlüssel/Wert-Feldern. Sie können diese auch als Schlüssel auf oberster Ebene übergeben. |

**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"])
```

**Antwort**

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

Die ID des neuen Kontakts befindet sich unter `data.contactId`. Die Listen, denen er hinzugefügt wurde, werden in `data.listsAdded` zurückgegeben.

> **Es werden keine Duplikate erstellt.** Wenn bereits ein Kontakt mit derselben Telefonnummer existiert, erstellt oder gibt der Erstellungsaufruf diesen **nicht** zurück. Die Antwort erfolgt mit dem HTTP-Status `200` und einem `error_code` von `409` im Body. Verzweigen Sie daher basierend auf `error_code` anstatt auf dem HTTP-Status:
>
> ```json
> { "success": false, "error_code": 409, "error": "A contact with this phone number already exists for the current user." }
> ```
>
> Um nach einem `error_code` von `409` mit einem bestehenden Kontakt zu arbeiten, suchen Sie ihn mit [Kontakt nach Telefonnummer oder E-Mail abrufen](#get-a-contact-by-phone-or-email) — `GET /contacts?phoneNumber=...` — und verwenden Sie die zurückgegebene ID erneut.

> **Äquivalente WhatsApp-Schreibweisen zählen als dieselbe Nummer.** Einige Länder haben zwei gültige Schreibweisen für dieselbe Mobilfunkleitung, und WhatsApp meldet möglicherweise eine von beiden: Mexiko (`+52…` und die ältere `+521…`), Brasilien (mit oder ohne die neunte Ziffer) und Argentinien (mit oder ohne die `9` nach der `+54`). Die Duplikatprüfung bei der Erstellung und `GET /contacts?phoneNumber=` erfolgt über beide Schreibweisen hinweg, sodass Sie den bestehenden Kontakt zurückerhalten, egal welche Form Sie senden. Die auf dem Kontakt gespeicherte `phone_number` wird niemals überschrieben.

---

## Kontakt per Telefon oder E-Mail abrufen

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

Sucht einen einzelnen Kontakt und gibt das vollständige, angereicherte Kontaktobjekt zurück – einschließlich seiner Listen, Tags und Kampagnen, die in `{ id, name }`-Paare aufgelöst wurden, sowie der letzten ausgetauschten Nachricht.

Übergeben Sie **entweder** `phoneNumber` (im internationalen Format) **oder** `email`. Wenn Sie keines von beiden übergeben, schaltet dieser Endpunkt stattdessen in den Modus [Kontakte auflisten](#list-contacts) um.

**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"])
```

**Antwort**

```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"
    }
  }
}
```

Die Kontakt-ID wird sowohl auf der obersten Ebene (`contactId`) als auch innerhalb des Objekts (`contact.id`) zurückgegeben. Wenn keine Übereinstimmung gefunden wird, erhalten Sie einen `404` mit `{ "success": false, "message": "Contact not found" }`.

> **`avatarUrl`** ist das Profilfoto des Kontakts, das von WhatsApp oder Meta übernommen wird, wenn dieser Ihnen eine Nachricht sendet. Es ist schreibgeschützt: Sie können es nicht festlegen, und es ist `null` für Kontakte, die kein Foto haben oder die Sie über einen Kanal erreichen, der kein Foto bereitstellt. Betrachten Sie den Link als temporär, anstatt ihn zu speichern, da einige dieser Fotolinks ablaufen und automatisch aktualisiert werden. (Im Listen-Endpunkt unten wird derselbe Wert als `avatar_url` bezeichnet.)

> **Telefonnummern in URLs.** Ein `+`-Zeichen in einer Abfragezeichenfolge muss als `%2B` URL-kodiert sein, da es sonst als Leerzeichen gelesen wird. Die obigen Beispiele erledigen dies für Sie.

---

## Einen Kontakt per ID abrufen

`GET /contacts/{contactId}`

Wenn Sie die ID eines Kontakts bereits haben, können Sie ihn direkt abrufen. Die Antwortstruktur ist identisch mit der obigen Suche.

**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"])
```

Eine Kontakt-ID, die in Ihrem Konto nicht existiert, gibt einen `404` zurück.

---

## Kontaktstatistiken abrufen

`GET /contacts/{contactId}/stats`

Gibt aggregierte Nachrichtenstatistiken für einen Kontakt zurück: Summen, KI- vs. menschliche Antworten, verbrauchte Credits sowie Zeitstempel der ersten und letzten Nachricht.

**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"])
```

**Antwort**

```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` ist derselbe KI-Nachrichtenzähler, den die „Zurücksetzen“-Schaltfläche in der App für einen Kontakt auf Null setzt. `creditsUsed` ist der laufende Credit-Gesamtstand für diesen Kontakt, nicht nur die Zahlen dieser Antwort. Eine Kontakt-ID, die in Ihrem Konto nicht existiert, gibt einen `404` zurück.

---

## Kontakte auflisten

`GET /contacts`

Rufen Sie `GET /contacts` **ohne** `phoneNumber` und `email` auf, um durch alle Ihre Kontakte zu blättern, beginnend mit den neuesten. Jede Seite gibt kompakte Kontaktzusammenfassungen zurück (Listen, Tags und Kampagnen werden als ID-Arrays anstelle von vollständigen Objekten zurückgegeben) sowie einen `next_cursor`.

| Abfrageparameter | Beschreibung |
|---|---|
| `limit` | Seitengröße. Standardwert ist 50, Maximum 100. |
| `cursor` | Der `next_cursor`-Wert von der vorherigen Seite. Auf der ersten Seite weglassen. |
| `listId` | Optional. Nur Kontakte zurückgeben, die zu dieser Liste gehören. |

Um alle Seiten zu durchlaufen: Führen Sie den ersten Aufruf ohne Cursor aus und übergeben Sie dann den zurückgegebenen `next_cursor` als `cursor`. **Stoppen Sie, wenn `next_cursor` gleich `null` ist** – das bedeutet, dass es keine weiteren Ergebnisse gibt.

**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
```

**Antwort**

```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
**Hinweis:** Das Filtern nach einem `listId`, das in Ihrem Konto nicht existiert, gibt ein `404` zurück. Ein ungültiges `cursor` gibt ein `400` zurück.
:::


---

## Kontakte zählen

`GET /contacts/count`

Gibt zurück, wie viele Kontakte einem Filter entsprechen, inklusive einer Aufschlüsselung nach Kanal, ohne durch die Seiten blättern zu müssen. Dies ist der richtige Aufruf für jede „Wie viele“-Frage – für ein Dashboard-Element, eine Automatisierung oder eine Anfrage an Champ. Alle Filter sind optional, und die Kombination mehrerer Filter schränkt die Anzahl ein (ein Kontakt muss auf jeden von Ihnen gesendeten Filter zutreffen).

| Abfrageparameter | Beschreibung |
|---|---|
| `agentId` | Nur Kontakte, die diesem KI-Agenten zugewiesen sind. Übergeben Sie `none` für Kontakte ohne zugewiesenen Agenten (diese werden vom Standard-Agenten des Kanals beantwortet). |
| `channel` | Nur Kontakte auf diesem Kanal, z. B. `whatsapp`, `messenger`, `instagram`, `sms`, `email`, `chat_widget`. |
| `tag` | Nur Kontakte mit diesem Tag, anhand des Tag-**Namens** (Groß-/Kleinschreibung spielt keine Rolle). Ein Tag-Name, den Sie nicht haben, gibt `404` zurück. |
| `listId` | Nur Kontakte auf dieser Liste. |
| `botActive` | `true` oder `false` – nur Kontakte, deren KI-Assistent aktiviert oder deaktiviert ist. |
| `status` | Nur Kontakte mit diesem Status, z. B. `Lead`. |
| `rules` | Ein URL-kodiertes JSON-Regelobjekt, das dieselbe Struktur wie eine intelligente Liste verwendet (siehe [Die `smart_rules`-Struktur](#the-smart_rules-shape) weiter unten). Kann nicht mit den anderen Filtern kombiniert werden. |

Senden Sie gar keinen Filter, erhalten Sie die Gesamtzahl der Kontakte in Ihrem Konto.

**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"])
```

**Antwort**

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

`by_channel` schlüsselt dieselbe Gesamtzahl nach Kanal auf; Kontakte, die keinem Kanal zugeordnet sind, werden unter `none` gezählt. `filters` spiegelt die angewendeten Filter wider, sodass Sie überprüfen können, ob der Aufruf das gewünschte Ergebnis erzielt hat.

::: note
**Hinweis:** Das Senden von `rules` zusammen mit einem anderen Filter oder einem `rules`-Wert, der kein gültiges JSON ist, führt zu `400`. Ein Tag-Name oder eine Listen-ID, die in Ihrem Konto nicht existiert, führt zu `404`.
:::


---

## Kontakt aktualisieren

`PUT /contacts/{contactId}`

Aktualisiert einen bestehenden Kontakt. Nur die Felder, die Sie angeben, werden geändert – lassen Sie alles weg, was Sie nicht ändern möchten. Sie müssen mindestens ein Feld senden, andernfalls erhalten Sie eine `400` („Keine Felder zum Aktualisieren“).

| Feld | Beschreibung |
|---|---|
| `firstName` | Vorname. |
| `lastName` | Nachname. |
| `email` | E-Mail-Adresse. |
| `is_bot_active` | Ob der KI-Assistent auf diesen Kontakt antwortet. |
| `is_private` | Als privat markieren. Wenn dies auf `true` gesetzt wird, wird auch der KI-Assistent deaktiviert. |
| `do_not_disturb` | Automatisierte Kontaktaufnahme mit diesem Kontakt pausieren. Stoppt auch die KI-Antworten. |
| `follow_ups_disabled` | Alle automatisierten Nachfassaktionen für diesen Kontakt stoppen (Quick, Cycle und Cold-Lead), während die KI weiterhin auf gesendete Nachrichten antwortet. Nützlich, sobald jemand gekauft hat. Bleibt deaktiviert, bis Sie es wieder auf `false` setzen. |
| `lead_profile` | Freitext-Notizen zum Lead. |
| `custom_fields` | Ein Objekt mit benutzerdefinierten Feldern. **Wird pro Schlüssel zusammengeführt** – nur die von Ihnen gesendeten Schlüssel werden geschrieben, die restlichen bestehenden benutzerdefinierten Felder bleiben erhalten. Sie können benutzerdefinierte Feld-Schlüssel auch auf der obersten Ebene übergeben. |

**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"])
```

**Antwort**

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

> **Benutzerdefinierte Felder werden zusammengeführt, nicht ersetzt.** Das Senden von `{ "custom_fields": { "tier": "gold" } }` setzt nur `tier` – alle anderen benutzerdefinierten Felder des Kontakts bleiben unverändert. Um ein benutzerdefiniertes Feld vollständig für alle Kontakte zu entfernen, verwenden Sie [Benutzerdefiniertes Feld löschen](#delete-a-custom-field).

---

## Tags hinzufügen oder entfernen

`POST /contacts/{contactId}/tags`

Fügt Tags zu einem einzelnen Kontakt hinzu und/oder entfernt sie in einem einzigen Aufruf. Übergeben Sie Tag-**IDs** in `addTagIds` und `removeTagIds`. Mindestens eines der beiden Felder darf nicht leer sein.

Die Tags müssen bereits in Ihrem Konto existieren – erstellen Sie diese zuerst über den [Tags-Endpunkt](reference.md). Wenn der Kontakt oder ein referenzierter Tag nicht existiert, erhalten Sie eine `404`.

| Feld | Beschreibung |
|---|---|
| `addTagIds` | Array von Tag-IDs, die dem Kontakt hinzugefügt werden sollen. |
| `removeTagIds` | Array von Tag-IDs, die vom Kontakt entfernt werden sollen. |

**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"])
```

**Antwort**

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

---

## Ihre Tag-Bibliothek verwalten

Diese Endpunkte verwalten das Tag selbst — also das Umbenennen oder Löschen in Ihrem Konto — im Gegensatz zum Anwenden oder Entfernen eines Tags bei einem Kontakt (siehe [Tags hinzufügen oder entfernen](#add-or-remove-tags) oben). Jedes Tag in Ihrem Konto hat eine ID (`tagId`): diejenige, die im Tag-Manager Ihres Dashboards angezeigt wird, und diejenige, die als `data.tag_id` zurückgegeben wird, wenn Sie ein Tag mit `POST /tags` und einem JSON-Body von `{ "name": "..." }` erstellen (ohne `phoneNumber`, `email` oder `contactId`).

### Ein Tag aktualisieren

`PUT /tags/{tagId}`

Senden Sie nur die Felder, die Sie ändern möchten.

| Feld | Beschreibung |
|---|---|
| `name` | Der Name des Tags. |

```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)" }'
```

**Antwort**

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

Ein `tagId`, das in Ihrem Konto nicht existiert, gibt einen `404` zurück.

### Ein Tag löschen

`DELETE /tags/{tagId}`

Löscht ein Tag anhand der ID. **Dies kann nicht rückgängig gemacht werden** — Kontakte, die dieses Tag tragen, verlieren es einfach. Das Löschen eines Tags, das bereits entfernt wurde (oder nie existierte), gibt `200` mit `deleted: 0` zurück anstelle eines `404`, da es nichts aufzulisten gibt.

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

**Antwort**

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

### Mehrere Tags gleichzeitig löschen

`DELETE /tags`

| Feld | Beschreibung |
|---|---|
| `tagIds` | Array der zu löschenden Tag-IDs (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"] }'
```

**Antwort**

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

IDs, die nicht existieren oder zu einem anderen Konto gehören, werden stillschweigend übersprungen und nicht in `deleted` gezählt.

---

## Flag massenhaft setzen

`POST /contacts/bulk-flag`

Setzt ein boolesches Flag für viele Kontakte gleichzeitig. Bis zu 500 Kontakt-IDs pro Anfrage. IDs, die in Ihrem Konto nicht existieren, werden übersprungen und in `skipped` gezählt.

| Feld | Beschreibung |
|---|---|
| `contactIds` | Array von Kontakt-IDs, die aktualisiert werden sollen (max. 500). |
| `field` | Welches Flag gesetzt werden soll. Eines von `bot_active` (KI-Assistent ein/aus), `dnd` (automatisierte Kontaktaufnahme pausieren), `spam`, `private`. |
| `value` | Der boolesche Wert, auf den das Flag gesetzt werden soll. |

**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"])
```

**Antwort**

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

---

## Kontakte massenhaft importieren

`POST /contacts/import`

Erstellt bis zu 500 Kontakte in einem Aufruf aus einem JSON-Array. Jeder Datensatz benötigt eine `phone_number` im internationalen Format; alles andere ist optional. Datensätze mit ungültigen Telefonnummern oder nicht unterstützten Kanälen werden **übersprungen** (nicht erstellt), und jeder übersprungene Datensatz wird mit seinem Index und dem Grund gemeldet – so können Sie nur die Fehler beheben und es erneut versuchen.

Telefonnummern, die bereits in Ihrem Konto existieren, werden standardmäßig als `duplicate` übersprungen. Senden Sie `updateExisting: true`, um diese Kontakte stattdessen zu **aktualisieren**: Die im Datensatz vorhandenen Felder überschreiben die des Kontakts (`first_name`, `last_name`, `email`, `lead_profile` und `custom_fields` werden Schlüssel für Schlüssel zusammengeführt), `tags` werden hinzugefügt und der Kontakt wird zu `listId` hinzugefügt. Kanal, Telefonnummer und Bot-Flags werden bei einem bestehenden Kontakt niemals geändert.

Sie können optional jeden importierten (oder aktualisierten) Kontakt mit `listId` zu einer Liste hinzufügen, einen `defaultChannel` für Datensätze festlegen, die keinen angeben, und Datensätze mit `tags` markieren (Tag-Namen – fehlende Tags werden erstellt, bestehende werden unabhängig von der Groß-/Kleinschreibung abgeglichen).

**Felder auf oberster Ebene**

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `contacts` | Ja | Array von Kontaktdatensätzen (max. 500). |
| `listId` | Nein | Liste, zu der jeder importierte (und aktualisierte) Kontakt hinzugefügt werden soll. Muss eine Liste in Ihrem Konto sein. |
| `defaultChannel` | Nein | Kanal, der auf Datensätze angewendet wird, die `channel` auslassen. Einer der Werte `whatsapp`, `sms`, `whatsapp_web`. Standardmäßig `whatsapp`. |
| `updateExisting` | Nein | `true`, um Kontakte zu aktualisieren, deren Telefonnummer bereits existiert, anstatt sie als `duplicate` zu überspringen. Standardmäßig `false`. |

**Felder pro Datensatz**

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `phone_number` | Ja | Telefonnummer im internationalen Format (ein führendes `+` wird hinzugefügt, falls es fehlt). |
| `first_name` | Nein | Vorname. |
| `last_name` | Nein | Nachname. |
| `email` | Nein | E-Mail-Adresse. |
| `channel` | Nein | Einer der Werte `whatsapp`, `sms`, `whatsapp_web`. Greift auf `defaultChannel` zurück. |
| `is_bot_active` | Nein | Ob der KI-Assistent antwortet. Standardmäßig `true`. |
| `is_private` | Nein | Als privat markieren. Standardmäßig `false`. |
| `lead_profile` | Nein | Freitext-Notizen zum Lead. |
| `custom_fields` | Nein | Objekt mit benutzerdefinierten Feld-Schlüsseln und -Werten. |
| `tags` | Nein | Array von Tag-Namen (ein einzelner `"a; b"`-String funktioniert ebenfalls). Tags, die nicht existieren, werden erstellt; bestehende werden unabhängig von der Groß-/Kleinschreibung abgeglichen. Max. 25 pro Datensatz. |

**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'])}")
```

**Antwort**

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

Wenn einige Datensätze nicht erstellt werden können, erscheinen sie in `skipped` mit dem Grund (hier ohne `updateExisting`, daher wird die existierende Nummer übersprungen):

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

Mit `updateExisting: true` meldet dieselbe Anfrage den existierenden Kontakt stattdessen unter `updated` / `updated_contact_ids`.

Mögliche Gründe für das Überspringen: `invalid_record`, `missing_phone_number`, `invalid_phone_number`, `invalid_channel`, `duplicate_in_request`, `duplicate`, `contact_limit_reached`, `create_failed`.

> **Tariflimits.** Wenn das Kontaktlimit Ihres Tarifs diese Anzahl neuer Kontakte nicht zulässt, wird die gesamte Anfrage vorab mit einem `403` abgelehnt. Wenn das Limit während des Vorgangs erreicht wird, werden die verbleibenden Datensätze mit dem Grund `contact_limit_reached` als übersprungen zurückgegeben.

---

## Kontakte aus einer CSV-Datei importieren

Für Importe, die größer sind als das, was der [Massenimport](#bulk-import-contacts) unterstützt (bis zu etwa 50.000 Zeilen), stellen Sie einen asynchronen Importauftrag für eine CSV-Datei in die Warteschlange, die sich bereits im Speicher Ihres Kontos befindet, und fragen Sie diesen ab, bis er abgeschlossen ist.

### Import starten

`POST /contacts/import-csv`

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `csvStoragePath` | Ja | Speicherpfad der CSV-Datei unter `users/{your account id}/imports/`, endend auf `.csv`. |
| `listName` | Ja | Erstellt (oder verwendet) eine Liste mit diesem Namen und fügt jeden importierten Kontakt hinzu. |
| `existingListRefs` | Nein | Array bestehender Listen-IDs, denen jeder importierte Kontakt ebenfalls hinzugefügt werden soll. |
| `defaultChannel` | Nein | Kanal, der auf Zeilen angewendet wird, die keinen angeben. |

```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"]
```

**Antwort** (`202` — der Import ist in der Warteschlange, noch nicht abgeschlossen)

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

> **Die Datei in den Speicher bringen.** Dieser Endpunkt startet und verfolgt den Importauftrag; er akzeptiert selbst keinen Upload. Die CSV-Datei muss sich bereits unter `csvStoragePath` befinden, bevor Sie ihn aufrufen – der CSV-Importer des Dashboards erledigt dies als ersten Schritt.

### Den Import-Job abfragen

`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"
```

**Antwort**

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

`status` durchläuft `queued` → `processing` → `completed` oder `failed` mit dem Grund in `error_message`. Eine `jobId`, die nicht in Ihrem Konto existiert, gibt einen `404` zurück.

---

## Kontakte exportieren

Startet einen asynchronen CSV-Export Ihrer Kontakte und gibt einen Auftrag zurück, den Sie auf Abschluss abfragen können.

### Export starten

`POST /contacts/export`

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `listId` | Nein | Exportiert nur Kontakte, die zu dieser Liste gehören. |
| `contactIds` | Nein | Exportiert nur diese spezifischen Kontakt-IDs. |

Wenn beide Felder leer gelassen werden, werden alle Kontakte Ihres Kontos exportiert.

```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"]
```

**Antwort** (`202` — der Export ist in der Warteschlange)

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

### Export-Job abfragen

`GET /contacts/export/{jobId}`

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

**Antwort**

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

> Sobald `status` `"completed"` ist, erhalten Sie `export_id` und `contact_count`. Die heruntergeladene CSV-Datei finden Sie auf der Export-Seite Ihres Dashboards.

---

## Nachricht an einen Kontakt senden

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

Sendet eine Nachricht an einen bestehenden Kontakt über den Kanal, den dieser bereits nutzt. Die Nachricht wird in die Warteschlange gestellt und im Hintergrund zugestellt – die Antwort bestätigt lediglich, dass sie akzeptiert wurde, nicht, dass sie bereits zugestellt ist.

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `body` | Ja | Der Text der zu sendenden Nachricht. |
| `mediaUrl` | Nein | URL einer Mediendatei zum Anhängen. |
| `mediaContentType` | Nein | MIME-Typ der angehängten Medien (z. B. `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"])
```

**Antwort**

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

> **Kann gerade nicht gesendet werden?** Wenn der Kontakt den „Nicht stören“-Modus oder den privaten Modus aktiviert hat oder sich auf keinem Kanal befindet, der ausgehende Nachrichten empfangen kann, wird die Anfrage mit einem `422` und einer erklärenden `error` abgelehnt.

Informationen zum Senden per Telefonnummer, Instagram-ID oder einer anderen Kanalidentität anstelle einer Kontakt-ID – sowie weitere allgemeine Informationen zum Messaging – finden Sie in der [Messages API](messages.md).

---

## Einen KI-Agenten einem Kontakt zuweisen

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

Verschiebt eine bestehende Unterhaltung ab der nächsten Nachricht zu einem anderen KI-Agenten. Dies entspricht der Funktion **KI-Agent zuweisen** im Chat-Menü und ist derselbe Schritt, den die Aktion **KI-Agent oder Kampagne zuweisen** in Automatisierungen verwendet.

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `agentId` | Ja | Die ID des KI-Agenten, der übernehmen soll, oder `null`, um die Zuweisung aufzuheben, damit die Unterhaltung zurück in Ihren Team-Posteingang geht. |
| `triggerAIResponse` | Nein | `true` veranlasst den neu zugewiesenen Agenten dazu, sofort auf die neuesten unbeantworteten Nachrichten des Kontakts zu antworten. Standardmäßig auf `false` gesetzt. |

> **Vorsicht bei `triggerAIResponse: true`** – es sendet dem Kontakt sofort eine Nachricht. Verwenden Sie es daher nur, wenn Sie möchten, dass der Kontakt jetzt kontaktiert wird. Bei Messenger und Instagram schlägt diese Nachricht fehl, wenn der Kontakt Ihnen zuletzt vor mehr als 24 Stunden geschrieben hat.

**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"])
```

**Antwort**

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

> Der Agent muss zum selben Konto gehören wie der Kontakt; andernfalls wird die Anfrage mit einem `404` oder `403` abgelehnt. Agenten-IDs finden Sie auf der Seite „KI-Agenten“ (die URL jedes Agenten endet mit seiner ID).

---

## KI-Agent vielen Kontakten zuweisen

`POST /contacts/bulk-assign-agent`

Verschiebt viele Konversationen in einem Aufruf zu einem anderen KI-Agenten – oder löscht die Zuweisung für alle mit `null`. Es handelt sich rein um eine Routing-Änderung: **Es wird keine Nachricht gesendet und der Agent antwortet niemandem**. Jeder Kontakt erhält einfach den neuen Agenten, wenn er das nächste Mal schreibt. (Deshalb gibt es hier kein `triggerAIResponse`.)

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `agentId` | Ja | Der KI-Agent, der übernehmen soll, oder `null`, um die Zuweisung zu löschen. |
| `contactIds` | Eines der drei | Bis zu 500 Kontakt-IDs zum Verschieben. |
| `filter` | Eines der drei | Wählen Sie die Kontakte auf dem Server aus, anstatt sie aufzulisten, beginnend mit den neuesten. Verwendet dieselben Schlüssel wie die Filter des Zähl-Endpunkts: `agentId` (oder `none`), `channel`, `tag`, `listId`, `botActive`, `status`. |
| `rules` | Eines der drei | Ein Regelobjekt für intelligente Listen – siehe [Die `smart_rules`-Struktur](#the-smart_rules-shape). |
| `limit` | Nein | Wie viele Kontakte in diesem Aufruf verschoben werden sollen, wenn Sie mit `filter` oder `rules` auswählen. 1 bis 500, Standardwert ist 500. |

Senden Sie genau eines von `contactIds`, `filter` oder `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"])
```

**Antwort**

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

`matched` ist die Anzahl der Kontakte, die die Auswahl insgesamt gefunden hat, `updated` wie viele durch diesen Aufruf verschoben wurden, `skipped` wie viele der von Ihnen gesendeten IDs in Ihrem Konto nicht gefunden wurden und `remaining` wie viele nach Abschluss dieses Aufrufs noch übereinstimmen.

**Alle verschieben.** Da ein Aufruf maximal 500 Kontakte verschiebt, erfordert eine große Gruppe mehrere Aufrufe. Verwenden Sie einen Filter, der nicht mehr auf einen Kontakt zutrifft, sobald er verschoben wurde – zum Beispiel `filter: { "agentId": "agent_abc123" }` bei der Zuweisung zu `agent_xyz789` – und wiederholen Sie genau denselben Aufruf, bis `remaining` als `0` zurückgegeben wird. Wenn Sie stattdessen `contactIds` übergeben, ist `remaining` immer `0`.

---

## Kontakt einer Abteilung zuweisen

`POST /contacts/{contactId}/department`

"Diesen Lead dem Vertrieb zuweisen" — ordnet einen Kontakt einer benannten Abteilung zu und weist ihn standardmäßig der Person in dieser Abteilung zu, die aktuell die wenigsten Kontakte hat. Dies ist unabhängig von der [Zuweisung eines KI-Agenten](#assign-an-ai-agent-to-a-contact): Eine Abteilung beantwortet die Frage „Welches Team ist zuständig?“, ein Agent beantwortet die Frage „Welche KI antwortet hier?“, und das Festlegen des einen löscht niemals das andere.

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `department_id` | Ja | Die Abteilung, der der Kontakt zugeordnet werden soll. Übergeben Sie `null`, um die Zuordnung aufzuheben. |
| `hand_to_member` | Nein | Weist den Kontakt zusätzlich der Person in der Abteilung mit der geringsten Auslastung zu. Standardwert ist `true`. Kontakte, die bereits jemandem gehören, werden nicht neu zugewiesen. |

**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"])
```

**Antwort**

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

`assigned_to` ist `null`, wenn der Kontakt bereits jemandem gehörte oder Sie `hand_to_member: false` übergeben haben.

---

## Kontakt über Kanäle hinweg verknüpfen

"Auf WhatsApp fortfahren" (oder SMS) sucht oder erstellt den Kontakt dieser Person auf einem anderen telefonbasierten Kanal und verknüpft beide miteinander, sodass der Rest der App sie als dieselbe Person erkennt.

### Mit einem anderen Kanal verknüpfen

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

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `channel` | Ja | Der Kanal, mit dem verknüpft werden soll. Einer der Werte `whatsapp`, `whatsapp_web`, `sms`. |
| `phoneNumber` | Nein | Telefonnummer, die für den neuen Kanal verwendet werden soll. Standardmäßig die eigene Nummer des Quellkontakts. |

```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" }'
```

**Antwort**

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

`created` gibt an, ob für den Zielkanal ein neuer Kontakt erstellt oder ein bestehender gefunden und verknüpft wurde. Ein zweiter Aufruf ist sicher – er gibt denselben `contact_id` mit `created: false` zurück, anstatt ein Duplikat zu erstellen.

Ein `422` bedeutet, dass das Konto diese Verknüpfung derzeit nicht durchführen kann: Der Kontakt befindet sich bereits in dieser Kanalfamilie, hat keine zu verwendende Telefonnummer oder es gibt keinen verbundenen Absender für den Zielkanal. Ein `409` bedeutet, dass die beiden Kontakte bereits mit zwei verschiedenen Personen verknüpft sind – heben Sie zuerst eine Verknüpfung auf.

### Verknüpfte Konversationen eines Kontakts auflisten

`GET /contacts/{contactId}/linked`

Gibt die anderen Konversationen zurück, die dieselbe Person wie dieser Kontakt sind. Ein nicht verknüpfter Kontakt gibt ein leeres Array zurück, keinen `404` – „diese Person hat keine anderen Kanäle“ ist ein normaler Zustand.

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

**Antwort**

```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"
      }
    }
  ]
}
```

### Verknüpfung eines Kontakts aufheben

`DELETE /contacts/{contactId}/link`

Entfernt diesen Kontakt einseitig aus seiner Person – alle anderen Kontakte, die noch mit dieser Person verknüpft sind, behalten ihre Verknüpfung, sodass das Aufheben einer von drei Verknüpfungen die Gruppe nicht auflöst.

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

**Antwort**

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

---

## Profilbild eines Kontakts abrufen

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

Ruft (und zwischenspeichert) das WhatsApp- oder Meta-Profilfoto des Kontakts bei Bedarf ab – dasselbe Foto, das als `avatarUrl` unter [Kontakt abrufen](#get-a-contact-by-phone-or-email) zurückgegeben wird, jedoch aktualisiert.

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

**Antwort**

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

`cached: true` bedeutet, dass die URL aus einem kürzlichen Abruf stammt und nicht aus einer neuen Anbieterabfrage – Bilder werden 7 Tage lang zwischengespeichert, und ein Kontakt, für den der Anbieter kein erreichbares Foto meldet, wird 24 Stunden lang als nicht verfügbar zwischengespeichert. Wenn kein Bild abgerufen werden kann, wird `avatar_url` weggelassen und `message` erklärt den Grund.

---

## Kontakte automatisch mit KI taggen

Führt die Tag-Regeln Ihres Kontos auf den vollständigen Konversationsverlauf eines oder mehrerer Kontakte aus und wendet Tags an (oder entfernt sie) – genau wie beim Echtzeit-Tagging während eines Live-Chats. Es gelten dieselben Regeln und dieselben Kreditkosten pro Tag.

### Einen Durchlauf starten

`POST /contacts/auto-tag`

| Feld | Erforderlich | Beschreibung |
|---|---|---|
| `scope` | Ja | `"contacts"`, um bestimmte Kontakte zu taggen, oder `"agent"`, um jede Konversation zu taggen, die derzeit von einem KI-Agenten bearbeitet wird. |
| `contact_ids` | Erforderlich, wenn `scope` gleich `"contacts"` ist | Array von Kontakt-IDs, 1 bis 500. |
| `agent_id` | Erforderlich, wenn `scope` gleich `"agent"` ist | Der KI-Agent, dessen Konversationen getaggt werden sollen. Wenn `scope` gleich `"contacts"` ist, ist dies optional und schränkt lediglich ein, welche Tag-Regeln des Agenten ausgeführt werden. |

```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"] }'
```

Ein **einzelner** Kontakt wird inline ausgeführt und liefert das Ergebnis sofort zurück:

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

**Zwei oder mehr** Kontakte (oder `scope: "agent"`) werden als Hintergrundjob ausgeführt und geben sofort `202` zurück:

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

### Einen Durchlauf abfragen

`GET /contacts/auto-tag/run`

Gibt den aktuellen (oder letzten) Durchlauf des Kontos zurück, sodass Sie den Fortschritt abfragen können, ohne `run_id` selbst nachverfolgen zu müssen.

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

**Antwort**

```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` ist `null`, wenn das Konto noch nie einen Durchlauf gestartet hat. `status` wechselt von `"running"` zu `"completed"` oder `"failed"`.

Pro Konto kann immer nur ein Massendurchlauf gleichzeitig aktiv sein – der Start eines zweiten Durchlaufs, während ein anderer läuft, gibt `409` mit `error_code: "auto_tag_run_in_progress"` zurück. Wenn bei einem Einzelkontakt-Durchlauf die Kredite ausgehen, wird `402` mit `error_code: "insufficient_credits"` zurückgegeben; ein Massendurchlauf stoppt stattdessen vorzeitig und meldet in `run`, wie weit er gekommen ist.

---

## Einen Kontakt löschen

`DELETE /contacts/{contactId}`

Löscht einen Kontakt dauerhaft anhand seiner ID, zusammen mit seinem Nachrichtenverlauf. **Dies kann nicht rückgängig gemacht werden.** Um mehrere Kontakte in einem einzigen Aufruf zu löschen, verwenden Sie unten [Kontakte löschen](#delete-contacts).

**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"])
```

**Antwort**

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

Eine Kontakt-ID, die in Ihrem Konto nicht existiert oder zu einem anderen Konto gehört, gibt einen `404` zurück.

---

## Kontakte löschen

`DELETE /contacts`

Löscht dauerhaft einen oder mehrere Kontakte anhand ihrer ID in einem einzigen Aufruf (bis zu 500 IDs). IDs, die in Ihrem Konto nicht existieren, werden übersprungen und in `skipped` gezählt. **Dies kann nicht rückgängig gemacht werden.**

| Feld | Beschreibung |
|---|---|
| `contactIds` | Array der zu löschenden Kontakt-IDs (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']}")
```

**Antwort**

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

---

## Ein benutzerdefiniertes Feld löschen

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

Entfernt einen benutzerdefinierten Feldschlüssel von **jedem** Kontakt in Ihrem Konto. Verwenden Sie dies, um nach dem Umbenennen oder Entfernen eines benutzerdefinierten Feldes aufzuräumen. Der Schlüssel darf nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten. Gibt zurück, wie viele Kontakte aktualisiert wurden. **Dies kann nicht rückgängig gemacht werden.**

**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")
```

**Antwort**

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

::: note
**Hinweis:** Ein Feld-Schlüssel mit nicht unterstützten Zeichen gibt ein `400` zurück.
:::


---

## Listen

Listen gruppieren Kontakte. Eine Liste ist entweder **statisch** (Sie entscheiden, wer darauf steht) oder **intelligent** (die Mitgliedschaft wird anhand von Regeln berechnet und automatisch auf dem neuesten Stand gehalten – siehe [Listen & Kontakte organisieren](../get-started/list-and-contact-management.md#smart-lists-auto-updating)).

| Feld | Beschreibung |
|---|---|
| `name` | Erforderlich bei der Erstellung. Bis zu 100 Zeichen. |
| `status` | `live` (Standard) oder `draft`. Kleingeschrieben. |
| `contact_ids` | Array von Kontakt-IDs, die der Liste hinzugefügt werden sollen. **Nur für statische Listen.** |
| `type` | `static` (Standard) oder `smart`. |
| `smart_rules` | Das Regelwerk – erforderlich, wenn `type` auf `smart` gesetzt ist. Siehe unten. |

### Eine Liste erstellen

`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" } }
          ]
        }
      }'
```

**Antwort**

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

Eine intelligente Liste wird **inline** in derselben Anfrage ausgewertet, sodass `evaluation` Ihnen genau anzeigt, wer letztendlich darauf gelandet ist. Bei einer statischen Liste ist `evaluation` `null`.

### Eine Liste aktualisieren

`PUT /lists/{listId}`

Senden Sie nur die Felder, die Sie ändern. Das Ändern von `smart_rules` wertet die Liste sofort neu aus und gibt dasselbe `evaluation`-Objekt zurück.

```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"] } ] } }'
```

Sie können eine Liste zwischen den beiden Typen umschalten:

- **Statisch → intelligent**: Senden Sie `{ "type": "smart", "smart_rules": { … } }`. Die Regeln greifen sofort.
- **Intelligent → statisch**: Senden Sie `{ "type": "static" }`. Die Regeln werden verworfen und alle Kontakte, die sich auf der Liste befinden, bleiben dort.

### Die Struktur von `smart_rules`

```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` (jede Bedingung muss wahr sein) oder `any` (mindestens eine).
- `conditions` — 1 bis 20 Bedingungen, jede mit maximal 100 Werten, Zeichenfolgen bis zu 200 Zeichen.

| `field` | `op` | `value` |
|---|---|---|
| `tags` | `has_any`, `has_all`, `has_none` | Array von Tag-IDs |
| `lists` | `in_any`, `not_in_any` | Array von Listen-IDs (**nur statische Listen** – eine intelligente Liste kann nicht aus einer anderen intelligenten Liste erstellt werden) |
| `channel` | `is_any`, `is_none` | Array von Kanälen |
| `status` | `is_any`, `is_none` | Array von Kontaktstatus |
| `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" }` |
| gleiche Datumsfelder | `before`, `after` | ISO-Datum (`"2026-01-01"`, Vergleich als ganze Tage) oder vollständiges ISO-Datum/Uhrzeit (`"2026-01-01T14:30:00Z"`, Vergleich auf den genauen Zeitpunkt) |
| gleiche Datumsfelder | `is_set`, `not_set` | — |
| `has_interacted_with_ai` | `is` | `true` / `false` – `true` entspricht Kontakten, denen die KI mindestens einmal (überhaupt) eine Nachricht gesendet hat |
| `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` | Zeichenfolge für die `contains`-Formulare |
| `current_campaign_id`, `assigned_agent` | `is_any`, `is_none`, `is_set`, `not_set` | Array von IDs für die `is_any` / `is_none`-Formulare |
| `custom_field` (plus ein `key`) | `eq`, `neq`, `contains`, `not_contains`, `is_set`, `not_set` | Zeichenfolge für die Wert-Formulare |

`not_within_last` findet auch Kontakte, bei denen das Datum nie festgelegt wurde („vor mehr als N Tagen, **oder nie**“), und Textvergleiche ignorieren die Groß-/Kleinschreibung.

**KI-Interaktion.** `has_interacted_with_ai` ist das lebenslange Flag: `true` für jeden Kontakt, dem Ihre KI mindestens eine Nachricht gesendet hat, `false` für alle anderen (einschließlich Kontakte, auf die nur Ihr Team geantwortet hat). Es wird bei der ersten Nachricht der KI an einen Kontakt gesetzt und nie gelöscht. Das Deaktivieren der KI-Antworten für den Kontakt oder das Verschieben in eine andere Kampagne setzt es daher nicht zurück. Für einen *Zeitraum* – „die Kontakte, die meine KI diesen Monat bearbeitet hat“, die übliche Abrechnungsfrage – verwenden Sie stattdessen den Bereich `last_ai_interaction_at`:

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

Verwechseln Sie beides nicht mit `is_bot_active` (die KI *darf* antworten, nicht dass sie es getan hat) oder `has_ever_responded` (der *Kontakt* hat geantwortet, an wen auch immer). Dieselben zwei Zeitstempel werden bei jedem Kontakt als `first_ai_interaction_at` / `last_ai_interaction_at` zurückgegeben, und der gesamte Regelsatz funktioniert auch mit `GET /contacts?rules=`, sodass Sie Übereinstimmungen zählen können, ohne eine Liste zu erstellen.

### Regelwerk in der Vorschau anzeigen

`POST /lists/preview`

Zählt und stichprobt die Kontakte, die ein Regelwerk erfassen würde, ohne etwas zu erstellen oder zu ändern. Verwenden Sie dies, um Regeln auf Plausibilität zu prüfen, bevor Sie sie speichern.

```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"] } ] } }'
```

**Antwort**

```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` enthält bis zu 10 Kontakte, sortiert nach der letzten Aktivität.

### Smart List jetzt erneut ausführen

`POST /lists/{listId}/evaluate`

Erzwingt eine sofortige Neubewertung (dasselbe, was **Jetzt aktualisieren** im Dashboard bewirkt). Smart Lists werden bereits bei Kontaktänderungen sowie alle 15 Minuten bei zeitbasierten Regeln aktualisiert; dies ist also nur erforderlich, wenn Sie das Ergebnis *sofort* benötigen.

**Antwort**

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

`evaluation.skipped: true` bedeutet, dass bereits eine Auswertung derselben Liste lief und dieser Aufruf keine Auswirkungen hatte.

### Smart Lists akzeptieren keine manuell hinzugefügten Mitglieder

Mitgliedschafts-Endpunkte geben **`409`** mit `"This is a smart list — its members are computed from its rules. Edit the rules instead."` zurück, wenn die Ziel-Liste eine Smart List ist. Dies betrifft `POST /contacts/lists`, `DELETE /contacts/lists`, `POST /contacts/lists/batch`, `contact_ids` bei `POST /lists` und `PUT /lists/{listId}` sowie die Auswahl einer Smart List als Ziel für einen CSV-Import. Ändern Sie stattdessen die Regeln.

Der Aufruf von `POST /lists/{listId}/evaluate` für eine **statische** Liste ist ebenfalls ein `409` — sie hat keine Regeln, die ausgeführt werden könnten.

---

## Kontakte-API-Fehler

Kontakt-Endpunkte geben das Standard-Fehler-Envelope zurück:

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

Einige Endpunkte enthalten auch `error_code`, was normalerweise dem HTTP-Status entspricht — die einzige Ausnahme ist der unten beschriebene Fall eines doppelten Kontakts, bei dem der HTTP-Status `200` ist und nur `error_code` den `409` enthält. Die spezifischen Codes für Kontakt-Endpunkte:

| Code | Wann dies bei einem Kontakt-Endpunkt auftritt |
|---|---|
| `400` | Ungültige Anfrage — ein fehlendes/ungültiges Feld, ein leerer Body, ein fehlerhafter Cursor oder mehr als 500 IDs in einem Batch. |
| `402` | Nicht genügend Credits, um einen KI-Tagging-Durchlauf für einen Kontakt (`error_code: "insufficient_credits"`) abzuschließen. |
| `404` | Der Kontakt, die Liste oder das Tag wurde in Ihrem Konto nicht gefunden. |
| `409` | Ein Kontakt mit dieser Telefonnummer existiert bereits (beim Erstellen). Wird als `error_code` im Body mit einem HTTP-Status von `200` zurückgegeben, daher hier nach `error_code` verzweigen. Wird auch zurückgegeben, wenn ein automatischer Bulk-Tagging-Durchlauf bereits läuft (`error_code: "auto_tag_run_in_progress"`) oder wenn die Verknüpfung eines Kontakts mit einem anderen Kanal zwei Kontakte verbinden würde, die bereits mit zwei verschiedenen Personen verknüpft sind. |
| `422` | Der Kontakt kann derzeit keine Nachricht empfangen (Nicht-stören-Modus, privat oder nicht unterstützter Kanal). Beim Kanal-Verknüpfungs-Endpunkt deckt dies auch fehlende Telefonnummern, eine nicht unterstützte Kanal-Kopplung oder keinen verbundenen Absender für den Zielkanal ab. |

Ein `403` bei einem Kontakt-Endpunkt kann auch ein Problem mit dem Kontaktlimit oder der Listenberechtigung bedeuten, anstatt ein Problem mit dem Tarifzugriff. Die gemeinsamen Codes, die jeder Endpunkt zurückgeben kann — `401`, `403` (Ihr Tarif beinhaltet keinen API-Zugriff), `429` (Ratenbegrenzung) und `500` — sind mit Hinweisen zur Wiederholung unter [Fehler & Paginierung](errors-and-pagination.md) aufgeführt.

---

## Nächste Schritte

- [Messages API](messages.md) — Nachrichten nach Kanalidentität senden und Konversationen verwalten.
- [API-Referenz](reference.md) — vollständige Endpunktliste, einschließlich Tags und Listen.
- [API-Zugriff](../integrations/api-access.md) — Authentifizierung, Ratenbegrenzungen und Fehlerbehandlung.
