
Mit der inDeal API legst du Leads aus anderen Systemen an, liest sie, hältst sie synchron und schreibst Aktivitäten wie Notizen oder Anrufe in die Timeline. Alles, was du dafür brauchst, findest du unter [Einstellungen -> API & Webhooks](https://app.indeal.ai/settings/api).

**Inhalt**
- [Schlüssel erstellen](#schluessel-erstellen)
- [Grundlagen](#grundlagen)
- [Lead anlegen](#lead-anlegen)
- [Lead lesen](#lead-lesen)
- [Lead aktualisieren](#lead-aktualisieren)
- [Leads auflisten](#leads-auflisten)
- [Aktivitäten](#aktivitaeten)
- [Felder](#felder)
- [Doppelte Leads](#doppelte-leads)
- [Fehlercodes](#fehlercodes)
- [Weiter](#weiter)


## Schlüssel erstellen

1. Öffne [Einstellungen -> API & Webhooks](https://app.indeal.ai/settings/api).
2. Klick auf **Neuen Schlüssel erstellen** und vergib einen Namen, der das angebundene System beschreibt.
3. Kopiere den Schlüssel sofort. **Kopiere den Schlüssel jetzt. Er wird nur einmal angezeigt und kann später nicht erneut abgerufen werden.**

![Der Dialog zum Erstellen eines Schlüssels mit dem Namen CRM-Sync](https://avhmrgajilnfyuvkhect.supabase.co/storage/v1/object/public/helpcenter/api-und-webhooks/api-key-create.png)

![Die einmalige Anzeige deines neuen Schlüssels mit Kopieren-Knopf](https://avhmrgajilnfyuvkhect.supabase.co/storage/v1/object/public/helpcenter/api-und-webhooks/api-key-created.png)

Danach zeigt die Liste nur noch die letzten vier Zeichen. Brauchst du den Schlüssel erneut, widerrufe den alten mit **Widerrufen** und erstelle einen neuen. Es sind bis zu 10 aktive Schlüssel möglich - so kannst du je System einen eigenen vergeben und einzeln widerrufen.

![Die Schlüsselliste mit dem maskierten Schlüssel CRM-Sync und den Verbindungsdaten darunter](https://avhmrgajilnfyuvkhect.supabase.co/storage/v1/object/public/helpcenter/api-und-webhooks/api-settings-overview.png)

## Grundlagen

| Angabe | Wert |
|---|---|
| Base-URL | `https://app.indeal.ai/api/v1` |
| Authentifizierung | Header `Authorization: Bearer indeal_...` |
| Format | JSON in Anfrage und Antwort |
| Limit | 600 Anfragen pro Minute je Schlüssel |

Über dem Limit antwortet die API mit Status 429 und dem Header `Retry-After: 60` - warte dann eine Minute.

## Lead anlegen

`POST /leads` legt einen Lead an. Mindestens eines von `email` oder `linkedin_url` (die Profil-URL des Kontakts) ist Pflicht.

```bash
curl -X POST https://app.indeal.ai/api/v1/leads \
  -H "Authorization: Bearer indeal_..." \
  -H "Content-Type: application/json" \
  -d '{
    "full_name": "Julia Weber",
    "email": "julia.weber@muster.de",
    "company_name": "Muster GmbH",
    "role_title": "Geschaeftsfuehrerin"
  }'
```

Antwort bei Erfolg (Status 201):

```json
{
  "status": "created",
  "lead": {
    "id": "0b0e...",
    "stage": "qualifying",
    "full_name": "Julia Weber",
    "email": "julia.weber@muster.de",
    "company_name": "Muster GmbH"
  }
}
```

Existiert der Lead schon, bekommst du Status 200 mit `"status": "duplicate"` und dem bestehenden Lead - siehe [Doppelte Leads](#doppelte-leads).

## Lead lesen

`GET /leads/{id}` liefert einen einzelnen Lead.

```bash
curl https://app.indeal.ai/api/v1/leads/0b0e... \
  -H "Authorization: Bearer indeal_..."
```

Antwort (Status 200): `{ "lead": { ... } }`. Eine unbekannte ID liefert Status 404.

## Lead aktualisieren

`PATCH /leads/{id}` ändert nur die Felder, die du schickst. `null` leert ein Feld. Mit `stage` wechselst du die Phase - die Verlaufshistorie in inDeal wird dabei automatisch geschrieben. `custom_data` wird zusammengeführt, nicht ersetzt: `null` als Wert löscht einen Key, alle anderen Keys bleiben.

```bash
curl -X PATCH https://app.indeal.ai/api/v1/leads/0b0e... \
  -H "Authorization: Bearer indeal_..." \
  -H "Content-Type: application/json" \
  -d '{ "stage": "meeting_scheduled", "next_step": "Demo vorbereiten" }'
```

Antwort (Status 200): `{ "lead": { ... } }`. Kollidiert die Änderung mit einem bestehenden Lead (E-Mail oder Profil-URL schon vergeben), kommt Status 409.

## Leads auflisten

`GET /leads` liefert Leads seitenweise, sortiert nach Änderungszeitpunkt.

```bash
curl "https://app.indeal.ai/api/v1/leads?updated_since=2026-09-01T00:00:00Z&limit=100" \
  -H "Authorization: Bearer indeal_..."
```

| Parameter | Bedeutung |
|---|---|
| `updated_since` | nur Leads, die seit diesem Zeitpunkt geändert wurden (ISO 8601) |
| `limit` | 1 bis 200, Standard 100 |
| `cursor` | `next_cursor` aus der vorigen Antwort |
| `order` | `asc` (Standard, älteste Änderung zuerst) oder `desc` (neueste zuerst) |

Antwort: `{ "leads": [...], "next_cursor": "..." }`. Ist `next_cursor` `null`, bist du am Ende. Der Cursor merkt sich die Sortierrichtung, du musst `order` beim Blättern nicht wiederholen. Für laufende Synchronisation: regelmäßig mit `updated_since` anfragen und mit `cursor` blättern.

### Lead per E-Mail oder Profil-URL finden

Du kennst die E-Mail-Adresse oder die Profil-URL und brauchst die `id`? Gib sie als Parameter mit. inDeal sucht dann genauso wie beim Anlegen: Groß- und Kleinschreibung spielen keine Rolle, bei der Profil-URL auch nicht `https://`, `www` oder ein Schrägstrich am Ende.

```bash
curl "https://app.indeal.ai/api/v1/leads?email=julia.weber@muster.de" \
  -H "Authorization: Bearer indeal_..."
```

| Parameter | Bedeutung |
|---|---|
| `email` | Treffer über die E-Mail-Adresse |
| `linkedin_url` | Treffer über die Profil-URL |

Gibst du beide an, bekommst du jeden Lead, auf den mindestens eines passt. Die Antwort ist `{ "leads": [...] }` ohne `next_cursor`, denn mehr als zwei Treffer kann es nicht geben. Passt nichts, ist die Liste leer. `updated_since` und `cursor` werden bei dieser Suche ignoriert. Eine ungültige E-Mail-Adresse oder URL liefert Status 400 mit dem Feldnamen.

## Aktivitäten

Jeder Lead hat eine Timeline. Über die API legst du dort Einträge an und liest sie.

### Aktivität anlegen

`POST /leads/{id}/activities` legt einen Timeline-Eintrag am Lead an. Schreibbar sind die Typen `note` (Notiz), `call` (Anruf), `email` (E-Mail) und `meeting_scheduled` (Termin). Mindestens eines von `title` oder `body` ist Pflicht. Fehlt `title`, bildet inDeal ihn aus den ersten 80 Zeichen von `body`.

| Feld | Pflicht | Hinweis |
|---|---|---|
| `type` | ja | `note`, `call`, `email`, `meeting_scheduled` |
| `title` | nein | max. 200 Zeichen |
| `body` | nein | Freitext, max. 10000 Zeichen, etwa das Gesprächsergebnis |
| `channel` | nein | `linkedin`, `email`, `meet`, `call` |
| `occurred_at` | nein | Zeitpunkt (ISO 8601), Standard jetzt, nicht in der Zukunft |

```bash
curl -X POST https://app.indeal.ai/api/v1/leads/0b0e.../activities \
  -H "Authorization: Bearer indeal_..." \
  -H "Content-Type: application/json" \
  -d '{ "type": "call", "body": "Kurzes Telefonat, Demo nächste Woche", "channel": "call" }'
```

Antwort bei Erfolg (Status 201):

```json
{
  "activity": {
    "id": "9f3a...",
    "type": "call",
    "title": "Kurzes Telefonat, Demo nächste Woche",
    "body": "Kurzes Telefonat, Demo nächste Woche",
    "quote": null,
    "channel": "call",
    "occurred_at": "2026-09-03T14:00:00+00:00",
    "created_at": "2026-09-03T14:00:01+00:00",
    "pinned": false,
    "edited_at": null
  }
}
```

Der Eintrag erscheint sofort in der Timeline des Leads, und Anrufe sowie E-Mails zählen als Kontaktpunkt. Die Typen `reply` (Antwort aus einer Kampagne) und `stage_change` (Phasenwechsel) schreibt inDeal selbst; schickst du sie, kommt Status 400. Eine unbekannte Lead-ID liefert Status 404.

### Timeline lesen

`GET /leads/{id}/activities` liefert alle Einträge des Leads, neueste zuerst, inklusive der nur lesbaren Typen `reply` und `stage_change`.

| Parameter | Bedeutung |
|---|---|
| `type` | nur ein Typ: `note`, `call`, `email`, `meeting_scheduled`, `reply`, `stage_change` |
| `limit` | 1 bis 200, Standard 50 |
| `cursor` | `next_cursor` aus der vorigen Antwort |

```bash
curl "https://app.indeal.ai/api/v1/leads/0b0e.../activities?type=reply&limit=50" \
  -H "Authorization: Bearer indeal_..."
```

Antwort: `{ "activities": [...], "next_cursor": "..." }`. Bei Antworten aus Kampagnen steht der Text in `quote`. `edited_at` zeigt, dass ein Eintrag nachträglich bearbeitet wurde. Ändern oder Löschen per API gibt es nicht.

Willst du über neue Einträge sofort informiert werden, statt die Timeline abzufragen, abonniere das Webhook-Ereignis `activity.created` - siehe [inDeal Webhooks](https://success.indeal.ai/hc/indeal/articles/indeal-webhooks-lead-aenderungen-senden).

## Felder

Pflichtregel: mindestens eines von `email` / `linkedin_url` beim Anlegen. Alles andere ist optional.

| Feld | Typ | Hinweis |
|---|---|---|
| `full_name` | Text | Name des Kontakts |
| `role_title` | Text | Position |
| `email` | Text | Dedup-Anker |
| `phone`, `mobile`, `company_phone` | Text | werden automatisch bereinigt |
| `linkedin_url` | Text | Profil-URL, Dedup-Anker |
| `company_name` | Text | Firma |
| `company_size` | Ganzzahl | Mitarbeiterzahl |
| `industry` | Text | Branche |
| `company_website`, `company_linkedin` | Text | Firmen-Links |
| `street`, `zip`, `city`, `country` | Text | Adresse |
| `next_step` | Text | nächster Schritt |
| `next_step_due` | Datum | Format `YYYY-MM-DD` |
| `est_value` | Zahl | geschätzter Wert |
| `description` | Text | Beschreibung |
| `lead_source` | Text | Herkunft |
| `sentiment` | Auswahl | `positive`, `neutral`, `negative` |
| `channel_direction` | Auswahl | `inbound`, `outbound` |
| `lost_reason` | Text | Verlustgrund |
| `stage` | Auswahl | `qualifying`, `schedule_meeting`, `meeting_scheduled`, `meeting_no_show`, `meeting_done`, `lead_lost` |
| `custom_data` | Objekt | eigene Felder, siehe [Eigene Felder](#eigene-felder) |

Jede Antwort enthält zusätzlich `url`, den Link zur Lead-Seite in inDeal (nur lesend). Unbekannte Felder lehnt die API ab - die Fehlermeldung nennt das betroffene Feld. Interne Felder (etwa Anreicherungs-Status) gibt die API weder heraus noch nimmt sie sie an.

## Eigene Felder

Eigene Felder legst du in inDeal unter [Layout anpassen](https://success.indeal.ai/hc/indeal/articles/layout-sections-und-eigene-felder) an, getrennt für Leads und Deals. Ihre Werte stehen im Objekt `custom_data`, der Schlüssel ist der API-Name des Feldes.

### Felder abrufen

```bash
curl "https://app.indeal.ai/api/v1/custom-fields?entity=lead" \
  -H "Authorization: Bearer indeal_..."
```

`entity` ist Pflicht (`lead` oder `deal`). Die Antwort enthält alle aktiven Felder in der Reihenfolge des Layouts, jeweils mit `key` (API-Name), `label`, `type`, bei Auswahlfeldern `options`, bei Währung `currency_code` und der `section`. Ausgeblendete Felder fehlen.

### Wertformat pro Typ

| `type` | Wert in `custom_data` | Beispiel |
|---|---|---|
| `text` | Text, max. 10.000 Zeichen, wird getrimmt | `"Rückruf nach Messe"` |
| `url` | Link, nur `http` oder `https`; ohne Schema wird `https://` ergänzt | `"https://indeal.ai"` |
| `number` | Zahl | `42` |
| `currency` | Zahl; die Währung steht in `currency_code` des Feldes | `5000` |
| `checkbox` | `true` oder `false` | `true` |
| `date` | `YYYY-MM-DD` | `"2026-10-15"` |
| `datetime` | ISO 8601 mit Offset | `"2026-10-15T09:30:00+02:00"` |
| `dropdown` | der API-Name einer Option, nicht ihr Name | `"gold"` |
| `multiselect` | Liste von Options-API-Namen, doppelte werden entfernt | `["messe", "empfehlung"]` |

`null` löscht einen Wert, ebenso ein leerer Text oder eine leere Liste. Pro Request sind höchstens 50 Schlüssel in `custom_data` erlaubt.

### Prüfung beim Schreiben

`POST` und `PATCH` auf Leads und Deals prüfen `custom_data` gegen die Felder des jeweiligen Moduls:

- Unbekannte Schlüssel und Schlüssel ausgeblendeter Felder werden ignoriert und in `warnings` gemeldet. Das ist kein Fehler, der Rest wird gespeichert.

```json
{ "lead": { ... }, "warnings": [{ "key": "partner_id", "code": "unknown_key" }, { "key": "alt", "code": "deleted_field" }] }
```

- Ungültige Werte bekannter Felder ergeben Status 422 mit `validation_error` und `details`. Dann wird nichts geschrieben, auch keine anderen Felder aus demselben Request.

```json
{
  "error": {
    "code": "validation_error",
    "message": "custom_data: invalid values.",
    "details": [
      { "key": "tarif", "code": "invalid_option" },
      { "key": "budget", "code": "invalid_type" }
    ]
  }
}
```

Mögliche Werte für `details.code`: `invalid_type`, `invalid_option`, `invalid_date`, `invalid_url`, `too_long`, `readonly_field`. `warnings` ist in jeder Schreibantwort enthalten, als leere Liste, wenn nichts ignoriert wurde.

## Doppelte Leads

Beim Anlegen prüft inDeal zuerst die Profil-URL, dann die E-Mail-Adresse - beide unabhängig von Groß- und Kleinschreibung. Gibt es bereits einen passenden Lead, wird KEIN neuer angelegt: du bekommst Status 200 mit `"status": "duplicate"` und dem bestehenden Lead samt seiner `id`.

Das macht den Endpoint gefahrlos wiederholbar: dieselbe Anfrage zweimal zu senden erzeugt nie zwei Leads. Willst du den bestehenden Lead ändern, nutze die `id` aus der Antwort mit `PATCH /leads/{id}`.

## Fehlercodes

Alle Fehler haben dieselbe Form: `{ "error": { "code": "...", "message": "..." } }`. Die Meldung ist immer auf Englisch und für Menschen gedacht, prüfe in deinem Code den `code`.

```json
{ "error": { "code": "not_found", "message": "Lead not found." } }
```

| Code | Status | Bedeutung |
|---|---|---|
| `unauthorized` | 401 | Schlüssel fehlt, ist ungültig oder widerrufen |
| `rate_limited` | 429 | Limit überschritten, warte die Sekunden aus `Retry-After` |
| `validation_error` | 400 | Eingabe ungültig, die Meldung nennt das Feld |
| `validation_error` | 422 | Ungültige Werte in `custom_data`, `details` nennt Feld und Grund, es wurde nichts geschrieben |
| `not_found` | 404 | Lead existiert nicht oder liegt im Papierkorb |
| `conflict` | 409 | Änderung kollidiert mit einem bestehenden Lead |
| `blacklisted` | 409 | Person oder Firma steht auf der [Blacklist](https://success.indeal.ai/hc/indeal/articles/blacklist-pflegen), es wurde nichts angelegt |
| `internal` | 500 | Unerwarteter Fehler, versuch es erneut |
| `internal` | 503 | Blacklist-Prüfung vorübergehend nicht möglich, es wurde nichts angelegt, versuch es später erneut |

## Weiter

- [inDeal API: Deals und Auswertungen](https://success.indeal.ai/hc/indeal/articles/indeal-api-deals-und-auswertungen)
- [inDeal Webhooks: Lead- und Deal-Änderungen an andere Systeme senden](https://success.indeal.ai/hc/indeal/articles/indeal-webhooks-lead-aenderungen-senden)
