# REST-API

> Chatte mit deinen intoCHAT-Agenten, lies ihre Gespräche, Leads und Wissensquellen, halte ihre Kontakte synchron und abonniere Webhooks, von deinem eigenen Server oder einem Automatisierungstool aus: API-Schlüssel, Scopes, Ratenlimits, Fehler, Paginierung und alle Endpunkte mit Beispielen.

Über die REST-API spricht dein eigener Server mit deinen Agenten. Sende eine Nachricht und bekomm die Antwort des Agenten, lies Gespräche und Leads, lege Kontakte an und aktualisiere sie, füge Wissensquellen hinzu oder entferne sie, und abonniere Webhooks für die Events eines Agenten, mit JSON über HTTPS. Die [Apps für Zapier und Make](/de/docs/zapier-and-make) bauen darauf auf. Eine Antwort über die API entsteht mit demselben Code wie eine Antwort im Chat-Widget, aus demselben Wissen und mit denselben Einstellungen.

Die API ist für Server gedacht. Sie sendet keine CORS-Header, eine Webseite auf einer anderen Website kann sie also nicht aufrufen, und dein API-Schlüssel darf nie in einem Browser oder in deinem Embed-Code auftauchen. Um einen Chat auf deiner Website zu zeigen, nutze das [Script-Tag](/de/docs/script-tag). Um Ereignisse zugeschickt zu bekommen, sobald sie passieren, nutze [Webhooks](/de/docs/webhooks).

## Welche Pläne die API enthalten

Die API ist in **Starter**, **Pro** und **Enterprise** enthalten. Im Plan **Free** kannst du keine Schlüssel erstellen.

Wechselt ein Konto zurück zu Free, bleiben seine Schlüssel in der Liste, aber jede Anfrage bekommt einen Fehler `403` mit dem Code `plan_required`. Sie funktionieren wieder, sobald das Konto im Plan Starter oder höher ist.

Jede Antwort, die ein Agent über die API schreibt, zählt als eine Nachricht deines Plans, genau wie eine Antwort im Widget, und zählt auf das [monatliche Nachrichtenlimit](/de/docs/limits-and-access#monatliches-nachrichtenlimit) des Agenten, falls du eines festgelegt hast. Daten lesen sowie Quellen hinzufügen oder löschen verbraucht keine Nachrichten. Siehe [Was als Nachricht zählt](/de/docs/plans-and-limits#was-als-nachricht-zahlt).

## API-Schlüssel erstellen

1. Klicke in der Seitenleiste unter **Settings** auf **API keys**.
2. Gib dem Schlüssel unter **Create a key** einen **Name**, an dem du ihn später wiedererkennst, zum Beispiel `CRM sync`.
3. Lass unter **Access** die Option **Every scope, including ones added later** gewählt, oder wähle **Only the scopes I choose** und hake die Scopes an, die dieser Schlüssel braucht. Siehe [Scopes](#scopes).
4. Klicke auf **Create key**.
5. Klicke auf **Copy**, leg den Schlüssel dort ab, wo dein Server ihn lesen kann, zum Beispiel in einer Umgebungsvariablen, und klicke auf **I've saved it**.

Ein Schlüssel besteht aus `ic_live_`, gefolgt von 32 Buchstaben und Ziffern. Er wird nur einmal angezeigt: intoCHAT speichert nur einen Fingerabdruck davon, niemand kann ihn dir also erneut anzeigen. Hast du ihn verloren, widerrufe ihn und erstelle einen neuen.

Ein Schlüssel gehört zum Konto, nicht zu der Person, die ihn erstellt hat. Er funktioniert im Rahmen seiner Scopes für jeden Agenten im Konto und funktioniert weiter, wenn diese Person das Team verlässt. Ein Konto kann bis zu 20 aktive Schlüssel haben.

Nur der Owner des Kontos und Admins können Schlüssel erstellen und widerrufen. Editoren und Viewer sehen die Liste der Schlüssel, nur mit deren ersten Zeichen. Siehe [Teammitglieder und Rollen](/de/docs/team).

### Die Schlüsselliste

Jeder Schlüssel zeigt seinen Namen, seine ersten Zeichen (zum Beispiel `ic_live_3f9A…`), seine Scopes, wann er erstellt und wann er zuletzt benutzt wurde. **Last used** wird höchstens einmal pro Minute aktualisiert.

### Schlüssel widerrufen

Klicke neben dem Schlüssel auf **Revoke** und bestätige. Der Schlüssel funktioniert ab seiner nächsten Anfrage nicht mehr, und das lässt sich nicht rückgängig machen. Widerrufe einen Schlüssel, sobald du glaubst, dass jemand anderes ihn haben könnte.

Widerrufst du einen Schlüssel, werden auch die Webhooks gelöscht, die er [abonniert](#webhook-abonnieren) hat, etwa für Zapier oder Make, samt Zustellverlauf: Sonst könnte sie nichts mehr entfernen. Webhooks, die du im Tab **Leads** hinzugefügt hast, sind nicht betroffen.

## Authentifizierung

Sende den Schlüssel bei jeder Anfrage im Header `Authorization`:

```bash
curl https://www.intochat.ai/api/v1/agents \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

Alle Endpunkte liegen unter `https://www.intochat.ai/api/v1`. Anfragen mit Body senden JSON mit `Content-Type: application/json`, und jede Antwort ist JSON, außer einer gestreamten Chat-Antwort. Die API nutzt keine Cookies und keine Login-Sitzung: Allein der Schlüssel entscheidet, was eine Anfrage darf.

Ein fehlender, fehlerhafter, unbekannter oder widerrufener Schlüssel bekommt einen Fehler `401` mit dem Code `unauthorized`.

## Scopes

Ein Scope erlaubt einem Schlüssel, eine Gruppe von Endpunkten zu nutzen. Ein Schlüssel, der mit **Every scope, including ones added later** erstellt wurde, kann jeden Endpunkt nutzen, auch künftig hinzukommende.

| Scope | Erlaubt |
| --- | --- |
| `agents:read` | [Agenten auflisten](#agenten-auflisten) und [einen Agenten abrufen](#agent-abrufen) |
| `chat` | [Eine Nachricht](#nachricht-senden) an einen Agenten senden. Jede Antwort zählt als Nachricht deines Plans. |
| `conversations:read` | [Gespräche auflisten](#gesprache-auflisten) und [ein Gespräch](#gesprach-abrufen) mit Verlauf abrufen |
| `leads:read` | [Leads auflisten](#leads-auflisten), [Kontakte auflisten](#kontakte-auflisten) und [einen Kontakt abrufen](#kontakt-abrufen) sowie die Lead-, Formular-, Buchungs- und Rücksende-Events von [Events auflisten](#events-auflisten) und [Beispiel-Events abrufen](#beispiel-events-abrufen) |
| `contacts:write` | [Einen Kontakt anlegen oder aktualisieren](#kontakt-anlegen-oder-aktualisieren), [einen Kontakt ändern](#kontakt-andern) und [einen Kontakt löschen](#kontakt-loschen) |
| `sources:read` | [Quellen auflisten](#quellen-auflisten) |
| `sources:write` | [Eine Quelle hinzufügen](#quelle-hinzufugen) und [eine Quelle löschen](#quelle-loschen), was auch das Training startet |
| `webhooks:write` | [Einen Webhook abonnieren](#webhook-abonnieren) und [einen Webhook abbestellen](#webhook-abbestellen) |

`conversations:read` deckt außerdem die Übergabe-, Live-Chat- und Neues-Gespräch-Events von [Events auflisten](#events-auflisten) ab.

Eine Anfrage an einen Endpunkt, für den der Schlüssel keinen Scope hat, bekommt einen Fehler `403` mit dem Code `insufficient_scope`. Um die Scopes eines Schlüssels zu ändern, erstelle einen neuen Schlüssel und widerrufe den alten.

## Ratenlimits

Jeder Schlüssel kann 60 Anfragen pro Minute stellen, über alle Endpunkte zusammen. Jede Antwort, die die Schlüsselprüfung bestanden hat, trägt diese Header:

| Header | Wert |
| --- | --- |
| `X-RateLimit-Limit` | `60` |
| `X-RateLimit-Remaining` | Verbleibende Anfragen in der aktuellen Minute |
| `X-RateLimit-Reset` | Wann die Minute endet, in Unix-Sekunden |

Über dem Limit bekommt die Anfrage einen Fehler `429` mit dem Code `rate_limited` und einen Header `Retry-After` mit den Sekunden, die du warten musst.

Für den Chat gelten zwei weitere Limits, die nicht vom Schlüssel abhängen: die Nachrichten deines Plans, und ein Agent beantwortet höchstens 1.000 Nachrichten pro Stunde, Widget und API zusammen. Darüber bekommt eine Chat-Anfrage einen Fehler `429` mit dem Code `agent_busy`.

## Fehler

Jeder Fehler hat dieselbe Form:

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key doesn't have the \"chat\" scope."
  }
}
```

Werte in deinem Code `code` aus; `message` erklärt den Fehler für Menschen und kann sich ändern.

| Status | Code | Bedeutung |
| --- | --- | --- |
| `400` | `invalid_request` | Der Anfrage fehlt etwas, oder sie hat einen falschen Wert. Die Meldung sagt, welchen. |
| `401` | `unauthorized` | Kein Schlüssel, oder der Schlüssel ist fehlerhaft, unbekannt oder widerrufen. |
| `403` | `insufficient_scope` | Dem Schlüssel fehlt der Scope, den dieser Endpunkt braucht. |
| `403` | `plan_required` | Das Konto ist im Plan Free. Die API ist ab Starter enthalten. |
| `403` | `message_limit_reached` | Das Konto hat in diesem Zeitraum alle Nachrichten seines Plans verbraucht. |
| `403` | `agent_cap_reached` | Der Agent hat das monatliche Nachrichtenlimit erreicht, das unter **Share**, **Limits and access** festgelegt ist. |
| `403` | `agent_private` | Der Agent ist privat und antwortet daher nur im Dashboard. Siehe [Private Agenten](/de/docs/allowed-domains#private-agenten). |
| `403` | `character_limit_exceeded` | Eine neue Quelle würde den Agenten über die Trainingszeichen seines Plans bringen. |
| `403` | `contact_limit_reached` | Ein neuer Kontakt würde den Agenten über die Kontakte pro Agent seines Plans bringen. |
| `404` | `not_found` | Diesen Agenten, dieses Gespräch, diese Quelle, diesen Kontakt oder diesen Webhook gibt es in diesem Konto nicht. |
| `409` | `conflict` | Ein anderer Kontakt des Agenten hat diese External ID oder E-Mail-Adresse schon. |
| `429` | `rate_limited` | Der Schlüssel hat in dieser Minute mehr als 60 Anfragen gestellt. |
| `429` | `agent_busy` | Der Agent beantwortet gerade zu viele Nachrichten. |
| `500` | `internal_error` | Auf unserer Seite ist etwas schiefgelaufen. Versuch es erneut. |
| `504` | `timeout` | Der Agent hat zu lange für die Antwort gebraucht. |

Ein Agent, ein Gespräch oder eine Quelle aus einem anderen Konto bekommt `404`, genau wie etwas, das es nicht gibt.

## Paginierung

[Gespräche auflisten](#gesprache-auflisten) und [Leads auflisten](#leads-auflisten) liefern jeweils eine Seite, die neuesten zuerst. Sie nehmen diese Query-Parameter an:

| Parameter | Inhalt |
| --- | --- |
| `limit` | Einträge pro Seite, von 1 bis 100. Standard ist 20. |
| `cursor` | Der `nextCursor` der vorherigen Seite, um die nächste zu holen. |
| `since` | Eine ISO-8601-Zeit, zum Beispiel `2026-10-01T00:00:00Z`. Es kommen nur Einträge mit Aktivität zu oder nach diesem Zeitpunkt: ein Gespräch mit einer Nachricht seitdem oder ein Lead, der seitdem gespeichert oder erneut abgeschickt wurde. |

Eine Seite sieht so aus:

```json
{
  "data": [ ],
  "hasMore": true,
  "nextCursor": "MjAyNi0xMC0wNFQwOTowMDowMC4wMDBafGNsdjEyMw"
}
```

Frag die nächste Seite mit `?cursor=` und diesem Wert ab, mit demselben `limit` und `since`, bis `hasMore` den Wert `false` hat und `nextCursor` den Wert `null`. Die Reihenfolge richtet sich fest nach dem Erstellungszeitpunkt, neue Aktivität während des Blätterns lässt also keinen Eintrag doppelt erscheinen oder fehlen. Für eine regelmäßige Synchronisierung speichere den Zeitpunkt, zu dem die letzte begonnen hat, und übergib ihn beim nächsten Mal als `since`.

## Agenten auflisten

`GET /api/v1/agents` · Scope `agents:read`

Alle Agenten im Konto, die neuesten zuerst.

```bash
curl https://www.intochat.ai/api/v1/agents \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "AGENT_ID",
      "name": "Acme Assistant",
      "private": false,
      "status": "trained",
      "trainedAt": "2026-10-02T09:00:00.000Z",
      "characters": 48210,
      "counts": { "conversations": 312, "messages": 2240, "leads": 41, "sources": 18 },
      "createdAt": "2026-09-01T09:00:00.000Z",
      "updatedAt": "2026-10-02T09:00:00.000Z"
    }
  ]
}
```

`status` ist `untrained`, `training`, `trained` oder `error` (das letzte Training ist fehlgeschlagen). `characters` gibt an, wie viele Trainingszeichen die trainierten Quellen des Agenten belegen.

## Agent abrufen

`GET /api/v1/agents/{agentId}` · Scope `agents:read`

Ein Agent in `data`, mit denselben Feldern wie unter [Agenten auflisten](#agenten-auflisten).

## Nachricht senden

`POST /api/v1/agents/{agentId}/chat` · Scope `chat`

Sendet eine Nachricht an den Agenten und liefert seine Antwort. Der Agent antwortet wie im Widget: aus seinem Wissen und seinen Anweisungen, mit deinen [API-Aktionen](/de/docs/api-actions), [eigenen Buttons](/de/docs/custom-buttons), [Chat-Formularen](/de/docs/chat-forms), der [Terminbuchung](/de/docs/booking), der [Übergabe per E-Mail](/de/docs/handoff) und dem [Live-Chat](/de/docs/live-chat), soweit du sie eingerichtet hast.

| Feld | Inhalt |
| --- | --- |
| `message` | Pflicht. Der Text, bis zu 10.000 Zeichen. |
| `conversationId` | Optional. Setzt ein Gespräch fort, das du über die API begonnen hast, mit der `conversationId` einer früheren Antwort. |
| `sessionId` | Optional. Deine eigene ID für die Person, für die du chattest, zum Beispiel deine User-ID, bis zu 100 Zeichen. Derselbe Wert setzt immer dasselbe Gespräch fort. Du kannst auch die `sessionId` einer früheren Antwort senden. |
| `stream` | Optional. `true` streamt den Text der Antwort; siehe [Streaming](#streaming). Standard ist `false`. |
| `timeZone` | Optional. Die IANA-Zeitzone der Person, zum Beispiel `Europe/Berlin`, für Terminzeiten. Ohne sie sind Zeiten in UTC. |

Ohne `conversationId` und `sessionId` startet jede Nachricht ein neues Gespräch.

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/chat \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Do you ship to Switzerland?", "sessionId": "customer-4821" }'
```

```json
{
  "reply": "Yes, we ship to Switzerland. Delivery takes 3 to 5 working days.",
  "conversationId": "CONVERSATION_ID",
  "sessionId": "icapi5f0c2b9e4d7a41f8a3c6e1b2d9f07a64",
  "sources": [
    { "title": "Shipping", "url": "https://www.example.com/shipping" }
  ],
  "followUps": ["How much does shipping cost?", "Can I track my order?"],
  "buttons": [],
  "products": [],
  "form": null,
  "leadForm": null,
  "booking": null,
  "liveChat": null
}
```

| Feld | Inhalt |
| --- | --- |
| `reply` | Die Antwort des Agenten als reiner Text mit Markdown, ohne die Steuerblöcke des Widgets. Leer, solange jemand aus deinem Team den Chat übernommen hat (siehe `liveChat`). |
| `conversationId` | Das Gespräch, wie unter [Gespräch abrufen](#gesprach-abrufen). |
| `sessionId` | Die Sitzung des Gesprächs in intoCHAT. Sie beginnt immer mit `icapi`. |
| `sources` | Die Seiten, auf die sich die Antwort stützt, als `title` und `url`, wenn **Show sources under replies** eingeschaltet ist. Seiten aus der [Websuche](/de/docs/web-search) stehen immer in der Liste, mit `"web": true`. |
| `followUps` | Vorgeschlagene nächste Fragen, wenn **Suggest follow-up questions** eingeschaltet ist. |
| `buttons` | [Eigene Buttons](/de/docs/custom-buttons), die der Agent gewählt hat, als `label` und `url`. |
| `products` | Produktkarten aus deinem [Shop](/de/docs/connect-your-store) oder aus API-Aktionen, mit `name`, `url` und, sofern bekannt, `image`, `price`, `compareAtPrice`, `currency` und `available`. |
| `form` | Ein [Chat-Formular](/de/docs/chat-forms), das der Agent angeboten hat, mit `formId`, `name`, `fields` und `submitLabel`, oder `null`. Die API kann es nicht abschicken; zeig es in deiner eigenen Oberfläche oder ignoriere es. |
| `leadForm` | Das [Lead-Formular](/de/docs/lead-collection) mit `message`, den Kontakt-`fields` und `form`, wenn der Agent nach Kontaktdaten gefragt hat, oder `null`. Kontaktdaten, die in einer Nachricht stehen, werden wie im Widget als Lead gespeichert, wenn **Save contact details written in the chat** eingeschaltet ist. |
| `booking` | Freie Terminzeiten mit `provider`, `eventTitle`, `eventLength` in Minuten, `timeZone` und `days`, jeweils mit `date` und `slots` als UTC-Zeiten, oder `null`. Die API kann keinen Termin buchen. |
| `liveChat` | `null` oder der Status des [Live-Chats](/de/docs/live-chat): `offered` (der Agent darf dein Team bitten dazuzukommen), `requested` (er hat es gebeten) oder `paused` (ein Teammitglied hat den Chat übernommen, deshalb hat der Agent nicht geantwortet). |

### Wie sich API-Chats vom Widget unterscheiden

- Das Gespräch wird wie ein Widget-Chat gespeichert und erscheint im Tab **Conversations** des Agenten, mit seinen Leads, Bewertungen und Statistiken. Es hat kein Land und keine Seite.
- [Erlaubte Domains](/de/docs/allowed-domains), [gesperrte Länder](/de/docs/limits-and-access#gesperrte-lander) und [Nachrichten pro Besucher](/de/docs/limits-and-access#nachrichten-pro-besucher) gelten nicht: Es gibt keine Seite und keine Adresse eines Besuchers, die sich prüfen ließe. Die Nachrichten deines Plans, das monatliche Limit des Agenten und das Ratenlimit des Schlüssels gelten.
- Ein [privater Agent](/de/docs/allowed-domains#private-agenten) lehnt API-Chats mit `agent_private` ab.
- [Client-seitige Aktionen](/de/docs/client-actions) laufen im Browser eines Besuchers, deshalb bekommt der Agent sie in API-Chats nicht angeboten. API-Aktionen, Buttons, Formulare und Terminbuchung funktionieren.
- Dateien lassen sich nicht anhängen, und es gibt keine temporären Chats.
- Nur Gespräche, die über die API begonnen wurden, lassen sich über die API fortsetzen. Der Agent sieht die letzten 40 Nachrichten des Gesprächs als Verlauf.
- Solange jemand aus deinem Team den Chat im [Live-Chat-Posteingang](/de/docs/live-chat#der-live-chat-posteingang) übernommen hat, wird deine Nachricht gespeichert, `reply` ist leer und `liveChat` ist `paused`. Die Antworten des Teammitglieds erscheinen im Gespräch: Lies sie mit [Gespräch abrufen](#gesprach-abrufen), dort haben sie einen `authorName`. Eine Nachricht während einer Übernahme verbraucht keine Nachrichten deines Plans.
- Eine Antwort, die länger als 55 Sekunden dauert, endet mit einem Fehler `504` und dem Code `timeout`. Sie kann trotzdem als Nachricht zählen.

### Streaming

Mit `"stream": true` ist die Antwort der Text der Antwort, während er geschrieben wird, als `text/plain`, ohne die JSON-Felder. Die Header `X-Conversation-Id` und `X-Session-Id` enthalten Gespräch und Sitzung, und `X-Live-Chat` den Live-Chat-Status, falls es einen gibt. Quellen, Buttons und die anderen Blöcke werden nicht gestreamt; lies sie danach mit [Gespräch abrufen](#gesprach-abrufen). Fehler kommen als JSON, wie ohne Streaming.

```bash
curl -N https://www.intochat.ai/api/v1/agents/AGENT_ID/chat \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "What are your opening hours?", "stream": true }'
```

## Gespräche auflisten

`GET /api/v1/agents/{agentId}/conversations` · Scope `conversations:read`

Die Gespräche des Agenten aus dem Widget und der API, die neuesten zuerst, ohne ihre Nachrichten. Nimmt `limit`, `cursor` und `since` an; siehe [Paginierung](#paginierung).

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/conversations?limit=50&since=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "CONVERSATION_ID",
      "sessionId": "8f14e45f-ceea-467a-9575-1a2b3c4d5e6f",
      "channel": "widget",
      "createdAt": "2026-10-04T09:00:00.000Z",
      "lastActivityAt": "2026-10-04T09:12:00.000Z",
      "country": "CH",
      "page": "https://www.example.com/pricing",
      "messageCount": 6,
      "topics": ["Pricing"],
      "sentiment": "positive",
      "liveChat": null,
      "csatScore": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

`channel` ist `api` bei einem Gespräch, das über die API begonnen wurde, sonst `widget` (Script-Tag, Inline-iframe, Direktlink und Playground). `topics` und `sentiment` setzt die [Themenanalyse](/de/docs/conversations-and-dashboard#themen-und-stimmung), wenn sie eingeschaltet ist. `liveChat` ist `null`, `requested`, `active` oder `ended`. `csatScore` ist die Bewertung des Besuchers von 1 bis 5 nach einem Live-Chat oder `null`.

## Gespräch abrufen

`GET /api/v1/conversations/{conversationId}` · Scope `conversations:read`

Ein Gespräch eines beliebigen Agenten im Konto, mit allen Nachrichten der Reihe nach.

```json
{
  "data": {
    "id": "CONVERSATION_ID",
    "agentId": "AGENT_ID",
    "sessionId": "icapi5f0c2b9e4d7a41f8a3c6e1b2d9f07a64",
    "channel": "api",
    "createdAt": "2026-10-04T09:00:00.000Z",
    "lastActivityAt": "2026-10-04T09:14:00.000Z",
    "country": null,
    "page": null,
    "topics": [],
    "sentiment": null,
    "handoffRequestedAt": null,
    "liveChat": { "status": "ended", "startedAt": "2026-10-04T09:05:00.000Z", "endedAt": "2026-10-04T09:13:00.000Z" },
    "csat": { "score": 5, "comment": "Quick and friendly.", "ratedAt": "2026-10-04T09:14:00.000Z" },
    "messages": [
      { "id": "MESSAGE_ID", "role": "assistant", "content": "Hello! How can I help you today?", "createdAt": "2026-10-04T09:00:00.000Z", "authorName": null, "sources": [] },
      { "id": "MESSAGE_ID", "role": "user", "content": "Can I change my delivery address?", "createdAt": "2026-10-04T09:01:00.000Z", "authorName": null, "sources": [] },
      { "id": "MESSAGE_ID", "role": "assistant", "content": "Yes, I'm on it. What's your order number?", "createdAt": "2026-10-04T09:06:00.000Z", "authorName": "Anna", "sources": [] }
    ]
  }
}
```

`role` ist `user` für den Besucher oder deine API-Nachrichten und `assistant` für den Agenten und dein Team. `authorName` ist der Vorname des Teammitglieds, das eine Antwort im [Live-Chat](/de/docs/live-chat) geschrieben hat, oder `null`, wenn der Agent sie geschrieben hat. `content` ist der Text ohne die Steuerblöcke des Widgets; die `sources` einer Antwort des Agenten listen die Seiten auf, die sie gezeigt hat. `handoffRequestedAt` ist der Zeitpunkt, zu dem der Agent das Gespräch per E-Mail an dein Team geschickt hat, oder `null`. `liveChat` und `csat` sind `null`, wenn es keinen Live-Chat oder keine Bewertung gab.

## Leads auflisten

`GET /api/v1/agents/{agentId}/leads` · Scope `leads:read`

Die Leads des Agenten, die neuesten zuerst. Nimmt `limit`, `cursor` und `since` an; siehe [Paginierung](#paginierung).

```json
{
  "data": [
    {
      "id": "LEAD_ID",
      "email": "jane@example.com",
      "phone": null,
      "name": "Jane",
      "customFields": [{ "id": "FIELD_ID", "label": "Company", "value": "Acme" }],
      "source": "form",
      "conversationId": "CONVERSATION_ID",
      "collectedAt": "2026-10-04T09:03:00.000Z",
      "lastSeenAt": "2026-10-04T09:03:00.000Z",
      "submissionCount": 1
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

`source` ist `form` für das Lead-Formular oder `chat` für Kontaktdaten, die in einer Nachricht standen. `customFields` nutzt die Beschriftungen, die dein Formular jetzt hat. `lastSeenAt` und `submissionCount` ändern sich, wenn dieselbe Person erneut absendet. `conversationId` ist `null`, wenn das Gespräch gelöscht wurde.

## Kontakte

Die [Kontakte](/de/docs/contacts) eines Agenten: verifizierte Besucher, Leads, importierte Kontakte und die, die du hier hinzufügst, jeder mit bis zu 50 eigenen Attributen. Zum Lesen braucht es `leads:read`, zum Anlegen, Ändern und Löschen `contacts:write`. Schlüssel, die mit **Every scope** erstellt wurden, haben beide.

Ein Kontakt sieht so aus:

```json
{
  "id": "CONTACT_ID",
  "agentId": "AGENT_ID",
  "externalId": "user-4711",
  "email": "jane@example.com",
  "phone": "+41441234567",
  "name": "Jane",
  "attributes": { "plan": "pro", "seats": 5, "vip": true },
  "source": "api",
  "firstSeenAt": "2026-10-04T09:03:00.000Z",
  "lastSeenAt": "2026-10-04T09:03:00.000Z",
  "createdAt": "2026-10-04T09:03:00.000Z",
  "updatedAt": "2026-10-04T09:03:00.000Z"
}
```

`source` ist `identity`, `lead`, `import` oder `api`: woher der Kontakt ursprünglich kam. `externalId` ist die User-ID der Person auf deiner Website. E-Mail-Adressen werden in Kleinbuchstaben gespeichert.

### Kontakte auflisten

`GET /api/v1/agents/{agentId}/contacts` · Scope `leads:read`

Die Kontakte des Agenten, die neuesten zuerst. Nimmt `limit`, `cursor` und `since` an (Kontakte, die zu oder nach diesem Zeitpunkt geändert wurden); siehe [Paginierung](#paginierung). Mit `email=` oder `external_id=` suchst du einen einzelnen Kontakt.

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/contacts?external_id=user-4711" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{ "data": [ { "id": "CONTACT_ID", "externalId": "user-4711", "email": "jane@example.com", "attributes": { "plan": "pro" } } ], "hasMore": false, "nextCursor": null }
```

`data` hat alle oben gezeigten Felder; hier fehlen einige.

### Kontakt anlegen oder aktualisieren

`POST /api/v1/agents/{agentId}/contacts` · Scope `contacts:write`

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/contacts \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "user-4711", "email": "jane@example.com", "name": "Jane", "attributes": { "plan": "pro", "seats": 5 } }'
```

| Feld | Inhalt |
| --- | --- |
| `external_id` | Bis zu 200 Zeichen. Sende dieses Feld oder `email`. |
| `email` | Eine gültige E-Mail-Adresse. |
| `phone` | 7 bis 15 Ziffern, optional mit `+` am Anfang. |
| `name` | Bis zu 100 Zeichen. |
| `attributes` | Ein Objekt aus Attributnamen und Werten. Namen beginnen mit einem Kleinbuchstaben und bestehen aus Kleinbuchstaben, Ziffern und Unterstrichen, bis zu 40 Zeichen; siehe [Attribute](/de/docs/contacts#attribute). Ein Wert ist Text mit bis zu 1.000 Zeichen, eine Zahl oder `true`/`false`, und `null` entfernt das Attribut. |

Aktualisiert wird der Kontakt mit dieser `external_id`, sonst der mit dieser `email`, sonst wird ein neuer Kontakt angelegt. Ein Kontakt nur mit E-Mail-Adresse, der so gefunden wird, bekommt die `external_id`. Die Felder, die du sendest, ersetzen die gespeicherten, ein Feld mit `null` wird geleert, und Felder, die du weglässt, bleiben, wie sie sind. Attribute werden zusammengeführt: Die gesendeten werden gesetzt, die anderen bleiben erhalten.

```json
{ "data": { "id": "CONTACT_ID", "externalId": "user-4711", "email": "jane@example.com", "attributes": { "plan": "pro", "seats": 5 } }, "result": "created" }
```

`result` ist `created` (Status `201`) oder `updated` (Status `200`). Eine E-Mail-Adresse, die zu einem Kontakt mit einer anderen External ID gehört, oder eine External ID oder E-Mail-Adresse, die ein anderer Kontakt schon hat, bekommt einen Fehler `409` mit dem Code `conflict`. Ein neuer Kontakt über die [Kontakte pro Agent](/de/docs/plans-and-limits#kontakte) deines Plans hinaus bekommt einen Fehler `403` mit dem Code `contact_limit_reached`.

### Kontakt abrufen

`GET /api/v1/contacts/{contactId}` · Scope `leads:read`

Ein Kontakt eines beliebigen Agenten im Konto, als `{ "data": { … } }`.

### Kontakt ändern

`PATCH /api/v1/contacts/{contactId}` · Scope `contacts:write`

Nimmt dieselben Felder an wie [Kontakt anlegen oder aktualisieren](#kontakt-anlegen-oder-aktualisieren), alle optional, und antwortet mit dem Kontakt als `{ "data": { … } }`. Ein Kontakt muss eine External ID, eine E-Mail-Adresse oder eine Telefonnummer behalten.

### Kontakt löschen

`DELETE /api/v1/contacts/{contactId}` · Scope `contacts:write`

```json
{ "data": { "id": "CONTACT_ID", "deleted": true } }
```

Die Leads und Gespräche des Kontakts bleiben erhalten.

## Quellen auflisten

`GET /api/v1/agents/{agentId}/sources` · Scope `sources:read`

Die Wissensquellen des Agenten in der Reihenfolge seines Tabs **Knowledge**, mit dem Trainingsfortschritt. Der Text einer Quelle ist nicht enthalten, außer Frage und Antwort eines Q&A-Paars.

```json
{
  "data": [
    {
      "id": "SOURCE_ID",
      "type": "qa",
      "title": "Do you ship to Switzerland?",
      "url": null,
      "filename": null,
      "question": "Do you ship to Switzerland?",
      "answer": "Yes, in 3 to 5 working days.",
      "characters": 71,
      "status": "trained",
      "error": null,
      "trainedAt": "2026-10-02T09:00:00.000Z",
      "createdAt": "2026-10-02T08:59:00.000Z",
      "updatedAt": "2026-10-02T09:00:00.000Z"
    }
  ],
  "training": { "total": 18, "trained": 18, "pending": 0, "processing": 0, "failed": 0, "done": true },
  "characters": { "used": 48210, "limit": 500000 }
}
```

`type` ist `website`, `file`, `text`, `qa` oder `notion`. `status` ist `pending`, `processing`, `trained` oder `failed`, mit dem Grund in `error`. `characters.limit` sind die Trainingszeichen pro Agent in deinem Plan.

## Quelle hinzufügen

`POST /api/v1/agents/{agentId}/sources` · Scope `sources:write`

Fügt einen [Textbaustein oder ein Q&A-Paar](/de/docs/text-and-qa) hinzu und startet das Training, wie im Tab **Knowledge**. Der Agent nutzt die neue Quelle, sobald sie trainiert ist; verfolge ihren `status` mit [Quellen auflisten](#quellen-auflisten).

Ein Textbaustein:

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/sources \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "text", "title": "Opening hours", "content": "We are open Monday to Friday, 9:00 to 17:00." }'
```

Ein Q&A-Paar:

```json
{ "type": "qa", "question": "Do you ship to Switzerland?", "answer": "Yes, in 3 to 5 working days." }
```

```json
{
  "data": { "id": "SOURCE_ID", "type": "text", "title": "Opening hours", "status": "pending", "characters": 44 },
  "result": "created",
  "training": { "total": 19, "trained": 18, "pending": 1, "processing": 0, "failed": 0, "done": false }
}
```

`data` hat alle Felder aus [Quellen auflisten](#quellen-auflisten); oben fehlen einige. `result` ist `created` (Status `201`), `updated`, wenn es schon ein Q&A-Paar mit derselben Frage gab, ohne Rücksicht auf Groß- und Kleinschreibung, das jetzt die neue Antwort hat, oder `unchanged`, wenn es genau diese Antwort schon hatte (beide Status `200`). Ein Textbaustein wird immer als neue Quelle hinzugefügt. Ein Titel und eine Frage können jeweils bis zu 200 Zeichen lang sein.

Die Quelle zählt auf die Trainingszeichen des Agenten. Eine Quelle, die das Limit deines Plans überschreiten würde, bekommt einen Fehler `403` mit dem Code `character_limit_exceeded`, und es wird nichts hinzugefügt. Siehe [Trainingszeichen pro Agent](/de/docs/plans-and-limits#trainingszeichen-pro-agent).

## Quelle löschen

`DELETE /api/v1/agents/{agentId}/sources/{sourceId}` · Scope `sources:write`

Löscht die Quelle und das, was der Agent daraus gelernt hat, sofort, wie das Löschen im Tab **Knowledge**. Sonst muss nichts neu trainiert werden.

```json
{ "id": "SOURCE_ID", "deleted": true, "training": { "total": 17, "trained": 17, "pending": 0, "processing": 0, "failed": 0, "done": true } }
```

## Webhook abonnieren

`POST /api/v1/agents/{agentId}/webhooks` · Scope `webhooks:write`

Abonniert einen Webhook für die Events des Agenten: einen REST-Hook, wie ihn die sofortigen Trigger von Zapier und Make nutzen. intoCHAT sendet dann jedes Event an die Adresse, wie unter [Webhooks](/de/docs/webhooks#was-gesendet-wird) beschrieben, signiert und wiederholt wie bei einem Webhook, der im Tab **Leads** hinzugefügt wurde.

| Feld | Inhalt |
| --- | --- |
| `url` | Pflicht. Die Adresse, an die die Events gehen. Wie im Tab **Leads** muss sie mit `https://` beginnen und auf einen öffentlichen Server zeigen; private und interne Netzwerkadressen werden abgelehnt. |
| `events` | Pflicht. Eines oder mehrere von `lead.created`, `handoff.requested`, `form.submitted`, `booking.created`, `live_chat.requested` und `return.requested`. Siehe [Events](/de/docs/webhooks#events). |
| `source` | Optional. `zapier`, `make` oder `api` (Standard): was der Tab **Leads** neben dem Webhook zeigt, zum Beispiel **via Zapier**. |

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/webhooks \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/intochat", "events": ["lead.created"], "source": "api" }'
```

```json
{
  "id": "WEBHOOK_ID",
  "agentId": "AGENT_ID",
  "url": "https://hooks.example.com/intochat",
  "events": ["lead.created"],
  "source": "api",
  "secret": "whsec_…",
  "createdAt": "2026-10-08T09:00:00.000Z"
}
```

Die Antwort hat den Status `201`. Bewahre `id` auf, um das Abonnement später zu beenden. `secret` ist das Signing Secret des Webhooks und kommt nur hier zurück; nutze es, um [die Signatur zu prüfen](/de/docs/webhooks#signatur-prufen), wenn dein Empfänger das kann. Tools, die keine Signaturen prüfen können, etwa Zapier und Make, können es ignorieren.

Ein abonnierter Webhook erscheint im Tab **Leads** des Agenten mit dem Kennzeichen **via Zapier**, **via Make** oder **via API**, wo der Owner ihn pausieren oder entfernen kann. Er merkt sich den Schlüssel, der ihn abonniert hat: [Widerrufst du diesen Schlüssel](#schlussel-widerrufen), wird er gelöscht. Ein Agent kann bis zu 25 abonnierte Webhooks haben, zusätzlich zu den 5, die im Tab **Leads** hinzugefügt werden; darüber hinaus bekommt die Anfrage einen Fehler `400`.

## Webhook abbestellen

`DELETE /api/v1/webhooks/{webhookId}` · Scope `webhooks:write`

Löscht einen Webhook, der über die API abonniert wurde, samt Zustellverlauf. Das kann jeder Schlüssel desselben Kontos. Ein Webhook, der im Tab **Leads** hinzugefügt wurde, der Webhook eines anderen Kontos oder einer, der schon entfernt wurde, bekommt `404`.

```json
{ "id": "WEBHOOK_ID", "deleted": true }
```

## Events auflisten

`GET /api/v1/agents/{agentId}/events/{event}` · Scope `agents:read`, dazu `leads:read` oder `conversations:read`

Die letzten Events einer Art des Agenten, die neuesten zuerst, jedes genau so aufgebaut wie der Inhalt einer [Webhook-Zustellung](/de/docs/webhooks#was-gesendet-wird). Automatisierungstools fragen diesen Endpunkt regelmäßig ab, statt auf einen Webhook zu warten, zum Beispiel um echte Beispieldaten zu zeigen. Nimmt `limit` an, von 1 bis 100 (Standard 20).

| `event` | Braucht außerdem | Aufgebaut aus |
| --- | --- | --- |
| `lead.created` | `leads:read` | Leads, die zuletzt gespeicherten zuerst |
| `form.submitted` | `leads:read` | Formulareinsendungen |
| `booking.created` | `leads:read` | Buchungen |
| `return.requested` | `leads:read` | Rücksendeanfragen |
| `handoff.requested` | `conversations:read` | Gespräche, die per E-Mail übergeben wurden |
| `live_chat.requested` | `conversations:read` | Gespräche, in denen der Agent dein Team gebeten hat dazuzukommen |
| `conversation.created` | `conversations:read` | Neue Gespräche. Für dieses Event gibt es keinen Webhook; es lässt sich nur auflisten. |

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/events/lead.created?limit=5" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "LEAD_ID",
      "event": "lead.created",
      "created_at": "2026-10-07T09:30:00.000Z",
      "agent": { "id": "AGENT_ID", "name": "Acme Assistant" },
      "data": {
        "leadId": "LEAD_ID",
        "agentId": "AGENT_ID",
        "agentName": "Acme Assistant",
        "email": "jane@example.com",
        "phone": null,
        "name": "Jane",
        "customFields": [],
        "source": "form",
        "conversationId": "CONVERSATION_ID",
        "collectedAt": "2026-10-07T09:30:00.000Z"
      }
    }
  ]
}
```

Die Unterschiede zu einer Zustellung:

- `id` ist die ID des Datensatzes, keine Zustell-ID, dasselbe Event hat also immer dieselbe `id`: die ID des Leads, der Einsendung, der Buchung, der Rücksendeanfrage oder des Gesprächs. Bei Übergaben und Live-Chat-Anfragen ist es die ID des Gesprächs, ein Doppelpunkt und die Zeit in Millisekunden, weil ein Gespräch später erneut fragen kann.
- `created_at` ist der Zeitpunkt, zu dem der Datensatz gespeichert wurde.
- Die Daten werden aus dem neu aufgebaut, was intoCHAT speichert. Die `summary` einer Übergabe wird nicht gespeichert, sie ist also `null`, und `test` ist `false`. Eine Übergabe, die ein [Helpdesk-Ticket](/de/docs/helpdesk-tickets) geöffnet hat, enthält ihr `ticket`, wie der Webhook. Das `providerStatus` einer Buchung ist `pending`, solange sie auf deine Bestätigung wartet, sonst `accepted`. Eine Rücksendeanfrage hat ihren aktuellen `status`, `requested` oder `handled`.
- Die Daten von `conversation.created` haben `conversationId`, `channel` (`widget` oder `api`), `page`, `country` und `createdAt`.

## Beispiel-Events abrufen

`GET /api/v1/agents/{agentId}/events/{event}/samples` · Scope `agents:read`

Bis zu 3 der letzten Events des Agenten, wie unter [Events auflisten](#events-auflisten), damit ein Tool immer Daten hat, aus denen es Felder zuordnen kann. Hat der Agent noch keine, oder fehlt dem Schlüssel der Scope, den dieses Event braucht (`leads:read` oder `conversations:read`), kommt stattdessen ein dokumentiertes Beispiel mit denselben Feldern, und `sample` ist `true`.

```json
{
  "data": [
    {
      "id": "sample_booking.created",
      "event": "booking.created",
      "created_at": "2026-10-07T09:30:00.000Z",
      "agent": { "id": "AGENT_ID", "name": "Acme Assistant" },
      "data": { "bookingId": "sample_booking", "eventTitle": "30 min intro call", "attendeeEmail": "jane@example.com" }
    }
  ],
  "sample": true
}
```

Die `data` des Beispiels haben alle Felder des Events; oben fehlen einige.

## Grenzen

- Die API ist ab Starter enthalten. Im Plan Free lassen sich keine Schlüssel erstellen, und bestehende bekommen `plan_required`.
- 60 Anfragen pro Minute und Schlüssel, und höchstens 20 aktive Schlüssel pro Konto.
- Jede Chat-Antwort zählt als eine Nachricht deines Plans. Ein Agent beantwortet höchstens 1.000 Nachrichten pro Stunde, Widget und API zusammen.
- Eine Chat-Antwort muss innerhalb von 55 Sekunden fertig sein.
- Quellen: nur Textbausteine und Q&A-Paare. Website-Seiten, Dateien und Notion-Seiten fügst du im Dashboard hinzu.
- Bis zu 25 abonnierte Webhooks pro Agent, zusätzlich zu den 5, die im Tab **Leads** hinzugefügt werden.
- Kontakte werden einzeln angelegt und aktualisiert. Um viele auf einmal hinzuzufügen, nutze [CSV-Datei importieren](/de/docs/contacts#csv-datei-importieren) im Dashboard.
- Endpunkte zum Erstellen oder Ändern von Agenten, zum Abschicken von Formularen, zum Buchen von Terminen oder zum Übernehmen von Live-Chats gibt es noch nicht.

## Wie es weitergeht

- Lass dir Leads und Übergaben an deinen Server schicken, sobald sie passieren: [Webhooks](/de/docs/webhooks).
- intoCHAT in Zapier oder Make nutzen: [Zapier und Make](/de/docs/zapier-and-make).
- Lass deinen Agenten beim Antworten deine eigene API aufrufen: [API-Aktionen](/de/docs/api-actions).
- Schreib gute Textbausteine und Q&A-Paare: [Text und Q&A](/de/docs/text-and-qa).
- Prüfe, was dein Plan enthält: [Pläne und Limits](/de/docs/plans-and-limits).
