API · v1
Die Call0-API.
Anrufe abrufen, Agenten verwalten, Ergebnisse als Webhook empfangen und freigeschaltete Outbound-Abläufe steuern — eine schlanke REST-API.
Einführung
Alle Endpoints liegen unter der Basis-URL https://www.call0.ai/api/v1. Requests und Responses sind JSON (Content-Type: application/json). Alle Ressourcen sind strikt auf das Konto deines API-Keys beschränkt.
Authentifizierung
Erstelle API-Keys im Dashboard unter Entwickler. Keys beginnen mit ck_live_ und werden nur einmal angezeigt. Jeder Key hat einen Scope: read (lesen) oder write (lesen + Anrufe starten).
Sende den Key als Bearer-Token:
curl https://www.call0.ai/api/v1/calls \ -H "Authorization: Bearer ck_live_IhrSchluessel"
Ohne gültigen Key antwortet die API mit 401, bei fehlendem Scope mit 403.
Anrufe auflisten
GET/v1/calls
Listet die Anrufe deines Kontos, neueste zuerst. Scope: read.
| Feld | Typ | Beschreibung |
|---|---|---|
| limit | integer | Anzahl pro Seite, max. 100 (Standard 20). |
| offset | integer | Versatz für Pagination (Standard 0). |
| agent_id | string | Optional: nur Anrufe dieses Agenten. |
curl "https://www.call0.ai/api/v1/calls?limit=2" \ -H "Authorization: Bearer ck_live_…"
Antwort · 200
{
"calls": [
{
"id": "3af91e9e-…",
"agent_id": "23061e5a-…",
"direction": "inbound",
"status": "completed",
"from": "+4917612345678",
"to": "+4921268943062",
"started_at": "2026-07-01T14:00:05Z",
"ended_at": "2026-07-01T14:03:41Z",
"duration_seconds": 216,
"sentiment": "positive",
"summary": "Tischreservierung für 4 Personen am Freitag, 19 Uhr.",
"action_items": ["Reservierung bestätigen"],
"callback_requested": false
}
],
"total": 1,
"limit": 2,
"offset": 0
}Anruf abrufen
GET/v1/calls/{id}
Ein einzelner Anruf — zusätzlich zur Liste mit vollständigem transcript. Scope: read.
curl https://www.call0.ai/api/v1/calls/3af91e9e-… \ -H "Authorization: Bearer ck_live_…"
Antwort · 200
{
"call": {
"id": "3af91e9e-…",
"status": "completed",
"duration_seconds": 216,
"summary": "Tischreservierung für 4 Personen …",
"transcript": [
{ "role": "agent", "text": "Willkommen bei MAKU …" },
{ "role": "caller", "text": "Ich hätte gern einen Tisch für vier." },
{ "role": "agent", "text": "Sehr gern — für wann?" }
]
}
}Existiert der Anruf nicht (oder gehört er einem anderen Konto): 404.
Anruf starten (Outbound)
POST/v1/calls/outbound
409 outbound_disabled.Startet einen ausgehenden Anruf über einen deiner Outbound-Agenten. Scope: write. Als agent_id ist der Outbound-Agent selbst oder sein Inbound-Elternteil erlaubt — Call0 löst automatisch zum Outbound-Agenten auf.
| Feld | Typ | Beschreibung |
|---|---|---|
| agent_id | string | Pflicht. ID des Agenten (Outbound oder zugehöriger Inbound). |
| to | string | Pflicht. Zielnummer im E.164-Format, z. B. +4917612345678. |
| mode | string | Optional. Demo-Modus "1", "2" oder "3" atomar für genau diesen Call. |
| variables | object | Optional. Dynamische Variablen fürs Gespräch (z. B. {"name": "Frau Weber"}). Werte max. 200 Zeichen. |
curl -X POST https://www.call0.ai/api/v1/calls/outbound \
-H "Authorization: Bearer ck_live_…" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "23061e5a-…",
"to": "+4917612345678",
"mode": "2",
"variables": { "name": "Frau Weber", "sendung": "DHL-4471" }
}'Antwort · 201
{
"success": true,
"call_id": "d9d63b86-…",
"call_sid": "CA7f3e…",
"mode": "2"
}Für Demo-Modi ist mode direkt in diesem Request die sichere Variante: Der Wert wird beim Platzieren festgeschrieben und kann sich bis zum Abheben nicht mehr ändern. Ohne Feld verwendet Call0 den zuvor über POST /v1/agents/{id}/mode gesetzten Default.
Den Verlauf holst du danach über GET /v1/calls/{call_id} — oder du lässt dir das Ergebnis per Webhook schicken. Fehlt dem Agenten eine Absender-Nummer: 422.
Agenten
GET/v1/agents
Listet die Agenten deines Kontos (ID, Name, Rolle, Sprache, Status).
POST/v1/agents
Legt einen Agenten an. Wichtigste Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
| name | string | Anzeigename / Firmenname für die Begrüßung. |
| system_prompt | string | Verhalten & Anweisungen des Agenten. |
| greeting | string | Begrüßung (Outbound: Eröffnungszeile). |
| language | string | Sprachcode, z. B. de-DE (Standard), en-US, fr-FR … |
| voice_id | string | Stimme (siehe Stimmen im Dashboard). |
Webhooks
Lege Webhook-Endpoints im Dashboard unter Integrationen → Webhook erstellen an. Nach jedem abgeschlossenen Gespräch senden wir call.completed als POST an deine URL. Außerdem kannst du call.missed, call.failed, handoff.requested, handoff.connected, intake.captured und booking.created abonnieren. Der Button Docs im Webhook-Dialog zeigt alle Payloads, Wiederholungen und Pausenregeln:
{
"event": "call.completed",
"call": {
"id": "3af91e9e-…",
"agent_id": "23061e5a-…",
"direction": "outbound",
"status": "completed",
"duration_seconds": 216,
"summary": "…",
"transcript": [ … ]
}
}Jede Zustellung ist signiert: Header X-Call0-Signature enthält sha256=<hex> — ein HMAC-SHA256 über timestamp.body mit deinem Webhook-Secret; der Zeitstempel steht in X-Call0-Timestamp. Verifikation in Node:
import crypto from 'crypto'
function verify(req, secret) {
const ts = req.headers['x-call0-timestamp']
const sig = req.headers['x-call0-signature'] // "sha256=…"
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(ts + '.' + req.rawBody)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
}Antworte mit einem 2xx-Status. Lehne Zustellungen ab, deren Zeitstempel älter als ein paar Minuten ist (Replay-Schutz).
Livechat (Chatbot-Übergabe)
Möchte ein Besucher im Chatbot einen Menschen, schickt Call0 den Chat an dein System (Webhook). Dein Team antwortet über die Livechat-API, die Antwort erscheint sofort im Chat-Fenster. Solange ein Mitarbeiter im Chat ist, antwortet der Chatbot nicht.
Einrichten unter Chatbot → Verhalten → Wenn ein Mensch helfen soll → Livechat per Webhook: Adresse deines Systems, Signatur-Schlüssel und ein API-Schlüssel mit dem Scope livechat:write. Dieser Schlüssel gilt nur für den Livechat dieses einen Chatbots; andere Schlüssel werden hier abgelehnt.
Endpunkte
Basis-URL https://www.call0.ai/api/v1/livechat, Header Authorization: Bearer ck_live_….
| Methode | Pfad | Wirkung |
|---|---|---|
| POST | /conversations/{id}/messages | Antwort eines Mitarbeiters an den Besucher senden (übernimmt den Chat, falls nötig) |
| POST | /conversations/{id}/takeover | Chat übernehmen: der Chatbot antwortet nicht mehr |
| POST | /conversations/{id}/release | Zurück an den Chatbot geben |
| POST | /conversations/{id}/close | Unterhaltung beenden |
| GET | /conversations/{id} | Status und letzte Nachrichten (?limit=50, höchstens 200) |
curl -X POST https://www.call0.ai/api/v1/livechat/conversations/6f1c2a9e-3b7d-4c55-9a1e-2d8f0b4c7e11/messages \
-H "Authorization: Bearer ck_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reply-4711" \
-d '{
"text": "Hallo, hier ist Anna vom Support. Wie kann ich helfen?",
"author": { "name": "Anna", "avatar_url": "https://deine-firma.de/team/anna.png" }
}'| Feld | Typ | Beschreibung |
|---|---|---|
| text | string | Pflicht, 1–4000 Zeichen. Reiner Text; Links mit https:// werden klickbar. |
| author.name | string? | Name des Mitarbeiters, bis 80 Zeichen. |
| author.avatar_url | string? | Bild des Mitarbeiters, nur https. |
| idempotency_key | string? | Oder Header Idempotency-Key. Gleicher Schlüssel → keine doppelte Nachricht. |
Eine Nachricht übernimmt den Chat automatisch. Antworten: 201 (neu) oder 200 (Wiederholung mit gleichem Idempotency-Key). Hat ein Teammitglied den Chat im Call0-Eingang übernommen, kommt 409.
Ereignisse an dein System
| Ereignis | Wann | Inhalt |
|---|---|---|
| handoff.requested | Besucher möchte einen Menschen | Unterhaltung, Besucher, Zusammenfassung, Verlauf, Links |
| message.created | Neue Besucher-Nachricht, während ein Mensch zuständig ist | Unterhaltung, Nachricht, Links |
| conversation.closed | Unterhaltung beendet (API oder Eingang) | closed_by, closed_at |
{
"id": "chatbot-handoff:6f1c2a9e-…:webhook:1761148800000",
"event": "handoff.requested",
"created_at": "2026-10-22T16:00:00.000Z",
"data": {
"conversation": {
"id": "6f1c2a9e-3b7d-4c55-9a1e-2d8f0b4c7e11",
"chatbot_id": "23061e5a-8c4f-4b2e-9d61-7a0e5f3c9b28",
"status": "waiting",
"created_at": "2026-10-22T15:58:12.000Z"
},
"visitor": {
"email": null,
"locale": "de",
"page_url": "https://deine-firma.de/preise"
},
"summary": "Der Besucher fragt nach einem Angebot für 20 Nutzer.",
"transcript": [
{
"id": "…",
"role": "visitor",
"text": "Was kostet das für 20 Nutzer?",
"author": null,
"created_at": "2026-10-22T15:58:12.000Z"
},
{
"id": "…",
"role": "bot",
"text": "Gern verbinde ich dich mit dem Team.",
"author": null,
"created_at": "2026-10-22T15:58:14.000Z"
}
],
"links": {
"dashboard": "https://www.call0.ai/dashboard/chatbot/inbox?agent=…&conversation=…",
"api": "https://www.call0.ai/api/v1/livechat/conversations/6f1c2a9e-3b7d-4c55-9a1e-2d8f0b4c7e11"
}
}
}Jede Zustellung trägt X-Call0-Event, X-Call0-Event-Id (gleich bei Wiederholungen), X-Call0-Timestamp und X-Call0-Signature = sha256=HMAC(secret, timestamp + "." + body). Antworte mit 2xx. Sonst versuchen wir es bis zu 5-mal mit wachsendem Abstand (1, 2, 4, 8 Min.). Ziele müssen öffentlich per HTTPS erreichbar sein.
import crypto from 'node:crypto'
// rawBody: der unveränderte Text der Anfrage
function verify(headers, rawBody, secret) {
const ts = headers['x-call0-timestamp']
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(ts + '.' + rawBody)
.digest('hex')
const given = Buffer.from(headers['x-call0-signature'] || '')
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300
return fresh && given.length === expected.length &&
crypto.timingSafeEqual(given, Buffer.from(expected))
}Grenzen
| Grenze | Wert | Antwort |
|---|---|---|
| Anfragen pro Schlüssel | 120 / min | 429 + Retry-After |
| Nachrichten pro Unterhaltung | 30 / min | 429 + Retry-After |
| Größe einer Anfrage | 16 KB | 413 |
| Fremde oder unbekannte Unterhaltung | — | 404 |
| Chatbot schreibt gerade | — | 409 bot_busy + Retry-After: 2 |
Fehler
| Feld | Typ | Beschreibung |
|---|---|---|
| 400 | invalid_request | Pflichtfeld fehlt oder Format ungültig (z. B. Nummer nicht E.164). |
| 401 | unauthorized | API-Key fehlt oder ist ungültig. |
| 403 | insufficient_scope | Key hat nicht den nötigen Scope (read/write). |
| 404 | not_found | Ressource existiert nicht oder gehört einem anderen Konto. |
| 422 | unprocessable | Anfrage verstanden, aber nicht ausführbar (z. B. keine Absender-Nummer). |
| 5xx | server_error | Unerwarteter Fehler — mit Backoff erneut versuchen. |
Fehler kommen als { "error": "…" } mit passendem HTTP-Status. Fragen? Schreib uns.