> ## Documentation Index
> Fetch the complete documentation index at: https://docs.telli.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrationsanleitung

> Migriere von den veralteten Kontakt-Endpunkten zur aktuellen API für Kontakte und Kontakteigenschaften

Die telli V2 API ist eine neue RESTful API zur Verwaltung von Kontakten und Kontakteigenschaften. Sie existiert parallel zur V1 API — beide bleiben vollständig funktionsfähig. Diese Anleitung zeigt dir die Unterschiede und wie du deine Integration migrierst.

## Voraussetzungen

* Ein telli Konto mit API-Zugang
* Ein API-Schlüssel aus deinem telli Dashboard (Einstellungen > API & Webhooks)

## Was sich geändert hat

Die V2 API bringt mehrere Verbesserungen gegenüber V1:

* **Typisierte Kontakteigenschaften** — Strukturierte, validierte Eigenschaften ersetzen untypisierte dynamische Variablen (siehe [Kontakteigenschaften](/de/platform/contact-properties) für Hintergrundinformationen)
* **Cursor-basierte Paginierung** — Effiziente Paginierung beim Auflisten von Kontakten
* **Strukturierte Fehler** — Einheitliche Fehlerantworten mit HTTP-Statuscodes und Fehlercodes

### Authentifizierung

Die Authentifizierung bleibt unverändert. Verwende denselben API-Schlüssel mit demselben `Authorization`-Header:

```bash theme={null}
Authorization: Bearer <your-api-key>
```

Die Basis-URL bleibt gleich — V2-Endpunkte sind unter dem `/v2/`-Präfix verfügbar.

***

## Endpunkt-Zuordnung

| V1-Endpunkt                                      | V2-Endpunkt                              | Hinweise                                          |
| ------------------------------------------------ | ---------------------------------------- | ------------------------------------------------- |
| `POST /v1/add-contact`                           | `POST /v2/contacts`                      | Gibt `201` mit vollständigem Kontaktobjekt zurück |
| `GET /v1/get-contact/:contactId`                 | `GET /v2/contacts/{id}`                  | Gibt typisierte Antwort mit `properties` zurück   |
| `GET /v1/get-contact-by-external-id/:externalId` | `GET /v2/external/contacts/{externalId}` | Gleiches Verhalten, neuer Pfad                    |
| `PATCH /v1/update-contact`                       | `PATCH /v2/contacts/{id}`                | Kontakt-ID wandert vom Body in den URL-Pfad       |
| `DELETE /v1/delete-contact/:contact_id`          | `DELETE /v2/contacts/{id}`               | Gibt `204` zurück (kein Body)                     |
| —                                                | `GET /v2/contacts`                       | **Neu.** Cursor-paginierte Kontaktliste           |
| `POST /v1/add-contacts-batch`                    | —                                        | Noch nicht in V2 verfügbar                        |
| `PATCH /v1/update-contacts-batch`                | —                                        | Noch nicht in V2 verfügbar                        |
| `POST /v1/get-contacts-batch`                    | —                                        | Noch nicht in V2 verfügbar                        |

***

## Feld-Zuordnung

Request- und Response-Felder wurden von snake\_case zu camelCase geändert:

| V1-Feld                 | V2-Feld                | Hinweise                                                                                        |
| ----------------------- | ---------------------- | ----------------------------------------------------------------------------------------------- |
| `external_contact_id`   | `externalId`           |                                                                                                 |
| `external_url`          | `externalUrl`          |                                                                                                 |
| `first_name`            | `firstName`            |                                                                                                 |
| `last_name`             | `lastName`             |                                                                                                 |
| `phone_number`          | `phoneNumber`          |                                                                                                 |
| `email`                 | `email`                | Unverändert                                                                                     |
| `salutation`            | `salutation`           | Unverändert                                                                                     |
| `timezone`              | `timezoneIana`         | Umbenannt zur Verdeutlichung                                                                    |
| `dynamic_variables`     | `properties`           | Siehe [Dynamische Variablen migrieren](#dynamische-variablen-zu-kontakteigenschaften-migrieren) |
| `contact_details`       | `properties`           | Siehe [Dynamische Variablen migrieren](#dynamische-variablen-zu-kontakteigenschaften-migrieren) |
| `contact_id` (Response) | `id` (Response)        |                                                                                                 |
| `created_at` (Response) | `createdAt` (Response) | ISO 8601 Datums-/Zeitzeichenkette                                                               |

***

## Antwortformat

V2-Antworten enthalten ein `type`-Feld und geben angereicherte Kontakteigenschaftsdaten zurück.

<CodeGroup>
  ```json V1 Response theme={null}
  {
    "contact_id": "b3f1a2c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
    "external_contact_id": "crm-123",
    "external_url": null,
    "first_name": "Max",
    "last_name": "Mustermann",
    "phone_number": "+4915112345678",
    "email": "max@example.com",
    "salutation": null,
    "timezone": "Europe/Berlin",
    "contact_details": {
      "appointment_date": "2026-03-15",
      "interest_level": "high"
    },
    "created_at": "2026-02-06T10:00:00.000Z",
    "status": "new",
    "call_attempts": 0,
    "next_call_at": null,
    "in_call_since": null,
    "reached_at": null
  }
  ```

  ```json V2 Response theme={null}
  {
    "id": "b3f1a2c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
    "type": "Contact",
    "externalId": "crm-123",
    "externalUrl": null,
    "firstName": "Max",
    "lastName": "Mustermann",
    "phoneNumber": "+4915112345678",
    "email": "max@example.com",
    "salutation": null,
    "timezoneIana": "Europe/Berlin",
    "properties": [
      {
        "key": "aB3xK9mP2nQ4",
        "value": "2026-03-15",
        "dataType": "date",
        "label": "Appointment Date"
      },
      {
        "key": "wLs7rT5yU8vX",
        "value": "high",
        "dataType": "select",
        "label": "Interest Level",
        "options": [
          { "value": "low", "label": "Low" },
          { "value": "medium", "label": "Medium" },
          { "value": "high", "label": "High" }
        ]
      }
    ],
    "createdAt": "2026-02-06T10:00:00.000Z",
    "updatedAt": "2026-02-06T10:00:00.000Z"
  }
  ```
</CodeGroup>

Wichtige Unterschiede in der Antwort:

* `contact_id` ist jetzt `id`
* `contact_details` (flaches Key-Value-Objekt) ist jetzt `properties` (Array aus typisierten, angereicherten Objekten)
* Jede Eigenschaft in der Antwort enthält ihren `dataType`, `label` und `options` (bei Select-Typen)
* Das `type`-Feld identifiziert den Ressourcentyp (`"Contact"`)
* Anruf-bezogene Felder (`status`, `call_attempts`, `next_call_at`, `in_call_since`, `reached_at`) sind nicht Teil der V2-Kontaktantwort

***

## Dynamische Variablen zu Kontakteigenschaften migrieren

Dies ist die bedeutendste Änderung zwischen V1 und V2. In V1 konntest du beliebige Key-Value-Daten über `dynamic_variables` oder `contact_details` an Kontakte anhängen, ohne vorherige Einrichtung. In V2 definierst du zuerst ein **Eigenschaftsschema** und verwendest dann den generierten Eigenschaftsschlüssel beim Setzen von Werten.

Einen vollständigen Überblick darüber, was Kontakteigenschaften sind und wie du sie in der telli-Oberfläche verwaltest, findest du unter [Kontakteigenschaften](/de/platform/contact-properties).

### So funktioniert es

1. **Eigenschaft definieren** — Erstelle eine Eigenschaftsdefinition mit einem Datentyp, Label und optionalen Einschränkungen über die API (oder über die telli-Oberfläche)
2. **Eigenschaftsschlüssel erhalten** — Jede Eigenschaft erhält einen automatisch generierten Schlüssel (z.B. `aB3xK9mP2nQ4`)
3. **Schlüssel bei Kontakten verwenden** — Beim Erstellen oder Aktualisieren von Kontakten übergibst du Eigenschaften als `[{key, value}]`-Paare mit den generierten Schlüsseln

### Beispiel: Vorher und Nachher

Angenommen, du hast diese dynamischen Variablen in V1 bei Kontakten gespeichert:

```json theme={null}
{
  "dynamic_variables": {
    "appointment_date": "2026-03-15",
    "interest_level": "high",
    "notes": "Interested in premium plan"
  }
}
```

Um auf V2 zu migrieren, definiere zunächst jede als typisierte Eigenschaft:

```bash theme={null}
# Create a date property for appointment dates
curl -X POST https://api.telli.com/v2/properties/contacts \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "dataType": "date",
    "label": "Appointment Date"
  }'
# Response: { "key": "aB3xK9mP2nQ4", "dataType": "date", ... }

# Create a select property for interest level
curl -X POST https://api.telli.com/v2/properties/contacts \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "dataType": "select",
    "label": "Interest Level",
    "options": [
      { "value": "low", "label": "Low" },
      { "value": "medium", "label": "Medium" },
      { "value": "high", "label": "High" }
    ]
  }'
# Response: { "key": "wLs7rT5yU8vX", "dataType": "select", ... }

# Create a text property for notes
curl -X POST https://api.telli.com/v2/properties/contacts \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "dataType": "string",
    "label": "Notes"
  }'
# Response: { "key": "jF2hG6kD9pN1", "dataType": "string", ... }
```

Verwende dann die zurückgegebenen Schlüssel beim Erstellen oder Aktualisieren von Kontakten:

```json theme={null}
{
  "properties": [
    { "key": "aB3xK9mP2nQ4", "value": "2026-03-15" },
    { "key": "wLs7rT5yU8vX", "value": "high" },
    { "key": "jF2hG6kD9pN1", "value": "Interested in premium plan" }
  ]
}
```

### Verfügbare Eigenschaftstypen

| API-Datentyp   | Beschreibung                                | Beispielwert             |
| -------------- | ------------------------------------------- | ------------------------ |
| `string`       | Freitext-Zeichenkette                       | `"Enterprise"`           |
| `number`       | Numerischer Wert                            | `42`                     |
| `boolean`      | Wahr oder falsch                            | `true`                   |
| `date`         | Kalenderdatum (YYYY-MM-DD)                  | `"2026-03-15"`           |
| `datetime`     | Datum mit Uhrzeit (ISO 8601)                | `"2026-03-15T14:30:00Z"` |
| `select`       | Einzelauswahl aus vordefinierten Optionen   | `"gold"`                 |
| `multi_select` | Mehrfachauswahl aus vordefinierten Optionen | `["german", "english"]`  |
| `phone_number` | Telefonnummer im E.164-Format               | `"+4915112345678"`       |
| `email`        | E-Mail-Adresse                              | `"sarah@example.com"`    |

### Systemeigenschaften

Einige Kontaktfelder werden in V2 als **Systemeigenschaften** dargestellt. Diese sind immer vorhanden und können nicht über die Properties-API geändert oder gelöscht werden:

| Key            | Datentyp      | Label        |
| -------------- | ------------- | ------------ |
| `externalId`   | string        | External ID  |
| `firstName`    | string        | First Name   |
| `lastName`     | string        | Last Name    |
| `phoneNumber`  | phone\_number | Phone Number |
| `email`        | email         | Email        |
| `timezoneIana` | string        | Timezone     |
| `externalUrl`  | string        | External URL |

Systemeigenschaften werden direkt als Top-Level-Felder am Kontakt gesetzt (z.B. `firstName`, `email`), nicht über das `properties`-Array.

***

## Migration Schritt für Schritt

### Kontakt erstellen

<CodeGroup>
  ```bash V1 theme={null}
  curl -X POST https://api.telli.com/v1/add-contact \
    -H "Authorization: Bearer <your-api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "external_contact_id": "crm-123",
      "first_name": "Max",
      "last_name": "Mustermann",
      "phone_number": "+4915112345678",
      "email": "max@example.com",
      "timezone": "Europe/Berlin",
      "dynamic_variables": {
        "appointment_date": "2026-03-15",
        "interest_level": "high"
      }
    }'
  ```

  ```bash V2 theme={null}
  curl -X POST https://api.telli.com/v2/contacts \
    -H "Authorization: Bearer <your-api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "externalId": "crm-123",
      "firstName": "Max",
      "lastName": "Mustermann",
      "phoneNumber": "+4915112345678",
      "email": "max@example.com",
      "timezoneIana": "Europe/Berlin",
      "properties": [
        { "key": "aB3xK9mP2nQ4", "value": "2026-03-15" },
        { "key": "wLs7rT5yU8vX", "value": "high" }
      ]
    }'
  ```
</CodeGroup>

**V1** gibt `{ "contact_id": "..." }` zurück. **V2** gibt `201` mit dem vollständigen Kontaktobjekt einschließlich angereicherter Eigenschaften zurück.

### Kontakt per ID abrufen

<CodeGroup>
  ```bash V1 theme={null}
  curl https://api.telli.com/v1/get-contact/<contact-id> \
    -H "Authorization: Bearer <your-api-key>"
  ```

  ```bash V2 theme={null}
  curl https://api.telli.com/v2/contacts/<contact-id> \
    -H "Authorization: Bearer <your-api-key>"
  ```
</CodeGroup>

### Kontakt per externer ID abrufen

<CodeGroup>
  ```bash V1 theme={null}
  curl https://api.telli.com/v1/get-contact-by-external-id/crm-123 \
    -H "Authorization: Bearer <your-api-key>"
  ```

  ```bash V2 theme={null}
  curl https://api.telli.com/v2/external/contacts/crm-123 \
    -H "Authorization: Bearer <your-api-key>"
  ```
</CodeGroup>

### Kontakte auflisten

Dieser Endpunkt ist **neu in V2** — es gibt kein V1-Äquivalent. Er gibt eine paginierte Liste von Kontakten mit Cursor-basierter Paginierung zurück.

```bash theme={null}
curl "https://api.telli.com/v2/contacts?limit=10" \
  -H "Authorization: Bearer <your-api-key>"
```

Antwort:

```json theme={null}
{
  "type": "ContactCollection",
  "data": [
    { "id": "...", "type": "Contact", "firstName": "Max", ... }
  ],
  "pageInfo": {
    "hasNextPage": true,
    "endCursor": "eyJjcmVhdGVkQXQiOi..."
  },
  "meta": {
    "totalCount": 142
  }
}
```

Um die nächste Seite abzurufen, übergib den `endCursor`-Wert als `cursor`-Query-Parameter:

```bash theme={null}
curl "https://api.telli.com/v2/contacts?limit=10&cursor=eyJjcmVhdGVkQXQiOi..." \
  -H "Authorization: Bearer <your-api-key>"
```

### Kontakt aktualisieren

In V1 übergibst du die `contact_id` im Request-Body. In V2 ist die Kontakt-ID Teil des URL-Pfads.

<CodeGroup>
  ```bash V1 theme={null}
  curl -X PATCH https://api.telli.com/v1/update-contact \
    -H "Authorization: Bearer <your-api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "contact_id": "<contact-id>",
      "first_name": "Maximilian",
      "dynamic_variables": {
        "interest_level": "medium"
      }
    }'
  ```

  ```bash V2 theme={null}
  curl -X PATCH https://api.telli.com/v2/contacts/<contact-id> \
    -H "Authorization: Bearer <your-api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "firstName": "Maximilian",
      "properties": [
        { "key": "wLs7rT5yU8vX", "value": "medium" }
      ]
    }'
  ```
</CodeGroup>

**Wichtiger Unterschied beim Update-Verhalten:**

* **V1** ersetzt das gesamte `dynamic_variables`-Objekt. Wenn du nur `{"interest_level": "medium"}` sendest, gehen alle anderen dynamischen Variablen verloren.
* **V2** führt Eigenschaften zusammen. Nur die Schlüssel, die du angibst, werden aktualisiert — alle anderen bestehenden Eigenschaften bleiben erhalten. Um eine Eigenschaft zu löschen, setze ihren Wert auf `null`.

### Kontakt löschen

<CodeGroup>
  ```bash V1 theme={null}
  curl -X DELETE https://api.telli.com/v1/delete-contact/<contact-id> \
    -H "Authorization: Bearer <your-api-key>"
  ```

  ```bash V2 theme={null}
  curl -X DELETE https://api.telli.com/v2/contacts/<contact-id> \
    -H "Authorization: Bearer <your-api-key>"
  ```
</CodeGroup>

**V1** gibt `{ "message": "Contact deleted successfully", "contact_id": "..." }` zurück. **V2** gibt `204 No Content` mit einem leeren Antwort-Body zurück.

***

## Kontakteigenschaften-API

V2 führt eine eigene API zur Verwaltung von Eigenschaftsdefinitionen ein. Du musst Eigenschaftsdefinitionen nur einmal pro Konto erstellen — sie gelten dann für alle Kontakte.

Informationen zur Verwaltung von Eigenschaften über die telli-Oberfläche findest du unter [Kontakteigenschaften](/de/platform/contact-properties).

### Alle Eigenschaftsdefinitionen auflisten

```bash theme={null}
curl https://api.telli.com/v2/properties/contacts \
  -H "Authorization: Bearer <your-api-key>"
```

Gibt sowohl Systemeigenschaften als auch deine benutzerdefinierten Eigenschaften zurück:

```json theme={null}
{
  "type": "ContactPropertyList",
  "data": [
    {
      "type": "ContactProperty",
      "key": "firstName",
      "dataType": "string",
      "source": "system",
      "label": "First Name",
      "createdAt": "2026-01-01T00:00:00.000Z",
      "updatedAt": "2026-01-01T00:00:00.000Z"
    },
    {
      "type": "ContactProperty",
      "key": "aB3xK9mP2nQ4",
      "dataType": "date",
      "source": "user",
      "label": "Appointment Date",
      "createdAt": "2026-02-06T10:00:00.000Z",
      "updatedAt": "2026-02-06T10:00:00.000Z"
    }
  ]
}
```

### Einzelne Eigenschaftsdefinition abrufen

```bash theme={null}
curl https://api.telli.com/v2/properties/contacts/aB3xK9mP2nQ4 \
  -H "Authorization: Bearer <your-api-key>"
```

### Eigenschaftsdefinition erstellen

```bash theme={null}
curl -X POST https://api.telli.com/v2/properties/contacts \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "dataType": "select",
    "label": "Customer Tier",
    "options": [
      { "value": "free", "label": "Free" },
      { "value": "pro", "label": "Pro" },
      { "value": "enterprise", "label": "Enterprise" }
    ]
  }'
```

### Eigenschaftsdefinition aktualisieren

Du kannst das Label, die Beschreibung und neue Optionen (bei Select-Typen) aktualisieren. Der Datentyp kann nicht geändert und bestehende Optionen können nicht entfernt werden.

```bash theme={null}
curl -X PATCH https://api.telli.com/v2/properties/contacts/aB3xK9mP2nQ4 \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Appointment Date (Updated)",
    "description": "The next scheduled appointment date"
  }'
```

***

## Wichtige Unterschiede im Überblick

1. **Eigenschaften erfordern eine vorherige Definition** — Du musst eine Eigenschaftsdefinition erstellen, bevor du sie bei Kontakten verwenden kannst. Unbekannte Eigenschaftsschlüssel werden mit einem `422`-Validierungsfehler abgelehnt.

2. **Eigenschaftsschlüssel werden systemseitig generiert** — Schlüssel wie `aB3xK9mP2nQ4` werden von telli beim Erstellen einer Eigenschaft zugewiesen. Verwende den [Endpunkt zum Auflisten von Eigenschaften](#alle-eigenschaftsdefinitionen-auflisten), um die Schlüssel deiner Eigenschaften nachzuschlagen.

3. **Updates führen Eigenschaften zusammen** — In V2 wirkt sich das Aktualisieren der Eigenschaften eines Kontakts nur auf die angegebenen Schlüssel aus. Bestehende Eigenschaften bleiben erhalten. Setze einen Wert auf `null`, um eine bestimmte Eigenschaft zu löschen. In V1 wurde beim Senden von `dynamic_variables` das gesamte Objekt ersetzt.

4. **Werte werden validiert** — V2 validiert Eigenschaftswerte gegen ihren Datentyp und ihre Einschränkungen (z.B. lehnt eine `date`-Eigenschaft `"not-a-date"` ab, eine `select`-Eigenschaft lehnt Werte ab, die nicht in der Optionsliste enthalten sind). V1 akzeptierte jeden Wert.

5. **Batch-Endpunkte sind in V2 noch nicht verfügbar** — Wenn du auf Batch-Operationen (`add-contacts-batch`, `update-contacts-batch`, `get-contacts-batch`) angewiesen bist, verwende vorerst weiterhin die V1-Endpunkte.

6. **V2 gibt angereicherte Eigenschaften zurück** — GET-Antworten enthalten den `dataType`, das `label` und die `options` für jeden Eigenschaftswert, sodass du keinen separaten Lookup benötigst, um Eigenschaftsdaten zu interpretieren.
