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.

FeldTypBeschreibung
limitintegerAnzahl pro Seite, max. 100 (Standard 20).
offsetintegerVersatz für Pagination (Standard 0).
agent_idstringOptional: 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

Outbound ist verfügbar. Für einen Anruf müssen ein Outbound-Agent und der Dialer des Projekts eingerichtet sein; bei deaktiviertem Dialer antwortet die API mit 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.

FeldTypBeschreibung
agent_idstringPflicht. ID des Agenten (Outbound oder zugehöriger Inbound).
tostringPflicht. Zielnummer im E.164-Format, z. B. +4917612345678.
modestringOptional. Demo-Modus "1", "2" oder "3" atomar für genau diesen Call.
variablesobjectOptional. 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:

FeldTypBeschreibung
namestringAnzeigename / Firmenname für die Begrüßung.
system_promptstringVerhalten & Anweisungen des Agenten.
greetingstringBegrüßung (Outbound: Eröffnungszeile).
languagestringSprachcode, z. B. de-DE (Standard), en-US, fr-FR …
voice_idstringStimme (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_….

MethodePfadWirkung
POST/conversations/{id}/messagesAntwort eines Mitarbeiters an den Besucher senden (übernimmt den Chat, falls nötig)
POST/conversations/{id}/takeoverChat übernehmen: der Chatbot antwortet nicht mehr
POST/conversations/{id}/releaseZurück an den Chatbot geben
POST/conversations/{id}/closeUnterhaltung 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" }
  }'
FeldTypBeschreibung
textstringPflicht, 1–4000 Zeichen. Reiner Text; Links mit https:// werden klickbar.
author.namestring?Name des Mitarbeiters, bis 80 Zeichen.
author.avatar_urlstring?Bild des Mitarbeiters, nur https.
idempotency_keystring?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

EreignisWannInhalt
handoff.requestedBesucher möchte einen MenschenUnterhaltung, Besucher, Zusammenfassung, Verlauf, Links
message.createdNeue Besucher-Nachricht, während ein Mensch zuständig istUnterhaltung, Nachricht, Links
conversation.closedUnterhaltung 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

GrenzeWertAntwort
Anfragen pro Schlüssel120 / min429 + Retry-After
Nachrichten pro Unterhaltung30 / min429 + Retry-After
Größe einer Anfrage16 KB413
Fremde oder unbekannte Unterhaltung—404
Chatbot schreibt gerade—409 bot_busy + Retry-After: 2

Fehler

FeldTypBeschreibung
400invalid_requestPflichtfeld fehlt oder Format ungültig (z. B. Nummer nicht E.164).
401unauthorizedAPI-Key fehlt oder ist ungültig.
403insufficient_scopeKey hat nicht den nötigen Scope (read/write).
404not_foundRessource existiert nicht oder gehört einem anderen Konto.
422unprocessableAnfrage verstanden, aber nicht ausführbar (z. B. keine Absender-Nummer).
5xxserver_errorUnerwarteter Fehler — mit Backoff erneut versuchen.

Fehler kommen als { "error": "…" } mit passendem HTTP-Status. Fragen? Schreib uns.