RevenueLab API v1LIVE

Die RevenueLab REST API gibt dir vollen programmatischen Zugriff auf Leads, Kunden, Projekte und Events. Baue Integrationen mit n8n, Make, Zapier oder eigenen Systemen.

🚀
Schnellstart: API-Key unter Settings → API-Zugang erstellen → Authorization: Bearer rl_live_...GET https://dashboard.revenue-lab.de/api?__path=projects aufrufen → fertig.
⚠️
Hinweis zur Base-URL (Stand 2026-06-05):
Die Plesk-Nginx-Konfiguration leitet /v1/... noch nicht an PHP weiter. Bis das aktiviert ist, bitte den funktionierenden Query-Stil nutzen:

Empfohlen: https://dashboard.revenue-lab.de/api?__path=<resource>
Geplant: https://api.revenue-lab.de/v1/<resource> (sobald Plesk angepasst)

Beide Routen landen am gleichen Endpunkt — nur die URL-Form unterscheidet sich.

Was kann die API?

  • Leads erstellen, auflisten, filtern, abrufen, aktualisieren
  • Notizen an Leads anheften und Verlauf auslesen
  • Lead → Kunde konvertieren (idempotent)
  • Kunden auflisten und abrufen
  • Projekte auflisten (fuer project_id bei Lead-Erstellung)
  • Event-Log aller API-Calls und Webhook-Zustellungen abfragen
  • Outbound Webhooks — bei Events automatisch Daten an Make / n8n / Zapier pushen
↔️
Zwei Richtungen:
REST API (Bearer-Token) = Daten in RevenueLab schreiben.
Webhooks (Automationen) = Events aus RevenueLab heraus an deine Systeme senden.

Authentifizierung

Jeder Request braucht einen Bearer-Token im Authorization-Header:

HTTP Header
Authorization: Bearer rl_live_abc123def456ghi789jkl012mno345pqrs6789

API-Key Regeln

EigenschaftWert
Formatrl_live_ + 40 Hex-Zeichen = 48 Zeichen total
VerwaltungSettings → API-Zugang
SichtbarkeitNur einmalig nach Erstellung im Klartext sichtbar
LimitMax. 10 aktive Keys pro Account
DeaktivierungJederzeit deaktivierbar oder loeschbar
⚠️
Sicherheit: API-Keys gehoeren niemals in Client-seitigen Code (Browser-JS, Mobile Apps) oder oeffentliche Repos. In Zapier/Make/n8n immer ueber die dortigen Secret-Felder hinterlegen.

Base URL & Format

Base URL
https://api.revenue-lab.de/v1

Endpunkte werden als REST-Pfade angesprochen:

  • GET /v1/leads — Leads auflisten
  • POST /v1/leads — Lead erstellen
  • GET /v1/leads/42 — Einzelner Lead
  • PATCH /v1/leads/42 — Lead aktualisieren
  • POST /v1/leads/42/notes — Notiz hinzufuegen
  • POST /v1/leads/42/convert — Lead zum Kunden machen

Alle Anfragen und Antworten verwenden JSON. Bei POST/PATCH den Header Content-Type: application/json setzen.

Erfolgreiche Antwort (Einzelobjekt)

JSON
{
  "success": true,
  "data": {
    "id": 42,
    "name": "Max Mustermann"
  }
}

Erfolgreiche Antwort (Liste mit Pagination)

JSON
{
  "success": true,
  "data":  [/* Array von Objekten */],
  "total": 84,
  "limit": 50,
  "offset": 0
}

HTTP Status Codes

200OK — Anfrage erfolgreich verarbeitet
201Created — Ressource erfolgreich erstellt (POST)
400Bad Request — Fehlende oder ungueltige Parameter
401Unauthorized — API-Key fehlt, ungueltig oder deaktiviert
404Not Found — Ressource existiert nicht
405Method Not Allowed — HTTP-Methode fuer diese Route nicht erlaubt

Fehlerbehandlung

Fehlerantworten haben immer "success": false und ein error-Objekt mit maschinenlesbarem Code:

Fehlerantwort
{
  "success": false,
  "error": {
    "code":    "missing_field",
    "message": "project_id ist erforderlich."
  }
}

Fehlercodes

missing_api_key
Kein oder falsches Key-Format im Header
invalid_api_key
Key existiert nicht in der Datenbank
revoked_api_key
Key wurde deaktiviert
missing_field
Ein Pflichtfeld fehlt im Request Body
missing_fields
Mehrere Pflichtfelder fehlen
invalid_status
Status-Wert nicht in der erlaubten Liste
invalid_json
Request Body ist kein gueltiges JSON
project_not_found
project_id existiert nicht fuer diesen Account
not_found
Angeforderte Ressource nicht gefunden
no_fields
PATCH ohne Felder zum Aktualisieren
method_not_allowed
HTTP-Methode fuer diese Route nicht erlaubt
unknown_resource
Unbekannter API-Pfad

Endpoint-Uebersicht

Alle verfuegbaren Endpunkte auf einen Blick. Base URL: https://api.revenue-lab.de/v1

GET /v1/projects — Alle Projekte auflisten

GET /v1/leads — Leads auflisten (Filter: project_id, status, search)
GET /v1/leads/{id} — Einzelnen Lead abrufen
POST /v1/leads — Neuen Lead erstellen
PATCH /v1/leads/{id} — Lead-Felder aktualisieren
GET /v1/leads/{id}/notes — Notizen-Verlauf abrufen
POST /v1/leads/{id}/notes — Notiz hinzufuegen
POST /v1/leads/{id}/convert — Lead zum Kunden konvertieren
GET /v1/leads/{id}/files — Dateien zu einem Lead auflisten
POST /v1/leads/{id}/files — Datei hochladen (multipart/Base64; optional as_contract=1 für Verträge-Tab)
GET /v1/leads/{id}/files/{fid}/download — Datei downloaden

GET /v1/customers — Kunden auflisten (Filter: search)
GET /v1/customers/{id} — Einzelnen Kunden abrufen
PATCH /v1/customers/{id} — Kunde aktualisieren (Standardfelder + custom)
GET /v1/customers/{id}/notes — Kunden-Feed / Notizen abrufen
POST /v1/customers/{id}/notes — Notiz hinzufuegen (auch bei Kunden ohne lead_id)

GET /v1/todos — ToDos auflisten (Filter: column_id, assigned_user_id, completed)
GET /v1/todos/{id} — Einzelne ToDo abrufen
POST /v1/todos — ToDo anlegen (feuert todo.created)
PATCH /v1/todos/{id} — ToDo aktualisieren (feuert todo.updated / todo.completed)
DEL /v1/todos/{id} — ToDo loeschen

GET /v1/events — Event-Log (Filter: category=api|webhook)
🤖
Fuer KI-Agents & Automationen: Alle Endpunkte akzeptieren application/json und geben JSON zurueck. Authentifizierung immer via Authorization: Bearer rl_live_.... Pagination ueber limit (max 200) + offset. Fehler liefern error.code (maschinenlesbar) + error.message (deutsch, menschenlesbar).

Projekte auflisten

Gibt alle Projekte zurueck. Die id wird als project_id beim Erstellen von Leads benoetigt.

GET https://api.revenue-lab.de/v1/projects

Antwort-Felder

FeldTypBeschreibung
idintegerEindeutige Projekt-ID
namestringProjektname
descriptionstring|nullProjektbeschreibung
statusstringactive oder inactive
lead_valuenumber|nullStandard-Lead-Wert in Euro
created_atdatetimeErstellungszeitpunkt
200 OK
{
  "success": true,
  "data": [
    {
      "id":          1,
      "name":        "Outbound B2B DACH",
      "description": "Kaltakquise Kampagne Q3 2026",
      "status":      "active",
      "lead_value":  250.00,
      "created_at":  "2026-04-01 09:00:00"
    },
    {
      "id":          2,
      "name":        "Inbound Website-Leads",
      "description": "Kontaktformular und Landingpages",
      "status":      "active",
      "lead_value":  180.00,
      "created_at":  "2026-05-15 10:30:00"
    }
  ],
  "total": 2
}
cURL
curl https://api.revenue-lab.de/v1/projects \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"
Python
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://api.revenue-lab.de/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

res = requests.get(f"{BASE}/projects", headers=headers)
projects = res.json()["data"]

for p in projects:
    print(f"{p['id']}: {p['name']}")

Leads auflisten

Paginierte Liste von Leads. Unterstuetzt Filter nach Projekt, Status und Freitext-Suche.

GET https://api.revenue-lab.de/v1/leads

Query-Parameter

ParameterTypBeschreibung
project_idintegerOptionalNur Leads aus diesem Projekt
statusstringOptionalExakter Status-Match, z.B. Neu oder Terminiert
searchstringOptionalFreitextsuche in Name, Firma, Telefon, E-Mail
limitintegerOptionalErgebnisse pro Seite (1–200, Standard: 50)
offsetintegerOptionalStartposition (Standard: 0)
200 OK
{
  "success": true,
  "data": [
    {
      "id":             42,
      "project_id":     1,
      "project_name":   "Outbound B2B DACH",
      "name":           "Max Mustermann",
      "company":        "Mustermann GmbH",
      "phone":          "+49 221 1234567",
      "email":          "max@mustermann-gmbh.de",
      "position":       "Geschaeftsfuehrer",
      "address":        "Musterstrasse 1, 50667 Koeln",
      "website":        "www.mustermann-gmbh.de",
      "status":         "Neu",
      "notes":          "Kontakt ueber Kontaktformular",
      "followup_at":    null,
      "pipeline_stage": "In Auftrag",
      "deal_value":     250.00,
      "created_at":     "2026-06-01 09:15:00",
      "updated_at":     "2026-06-01 09:15:00"
    },
    {
      "id":             43,
      "project_id":     1,
      "project_name":   "Outbound B2B DACH",
      "name":           "Lisa Schmidt",
      "company":        "Schmidt & Partner AG",
      "phone":          "+49 30 9876543",
      "email":          "l.schmidt@schmidt-partner.de",
      "position":       "Vertriebsleiterin",
      "address":        null,
      "website":        "www.schmidt-partner.de",
      "status":         "Terminiert",
      "notes":          "Termin am 10.06. um 14:00 Uhr",
      "followup_at":    "2026-06-10 14:00:00",
      "pipeline_stage": "Terminiert",
      "deal_value":     500.00,
      "created_at":     "2026-06-02 11:30:00",
      "updated_at":     "2026-06-04 16:20:00"
    }
  ],
  "total":  84,
  "limit":  50,
  "offset": 0
}
cURL
# Alle Leads
curl "https://api.revenue-lab.de/v1/leads" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"

# Filtern: Projekt 1, Status "Neu", Seite 2
curl "https://api.revenue-lab.de/v1/leads?project_id=1&status=Neu&limit=50&offset=50" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"

# Freitextsuche
curl "https://api.revenue-lab.de/v1/leads?search=Mustermann" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"
Python
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://api.revenue-lab.de/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

# Alle neuen Leads aus Projekt 1
res = requests.get(f"{BASE}/leads", headers=headers, params={
    "project_id": 1,
    "status": "Neu",
    "limit": 50
})
data = res.json()
print(f"{len(data['data'])} von {data['total']} Leads geladen")
JavaScript
const API_KEY = 'rl_live_DEIN_API_KEY';
const BASE    = 'https://api.revenue-lab.de/v1';

const params = new URLSearchParams({
  project_id: 1, status: 'Neu', limit: 50
});
const res = await fetch(`${BASE}/leads?${params}`, {
  headers: { 'Authorization': `Bearer ${API_KEY}` },
});
const { success, data, total } = await res.json();
console.log(`${data.length} von ${total} Leads geladen`);

Einzelnen Lead abrufen

GET https://api.revenue-lab.de/v1/leads/{id}
200 OK
{
  "success": true,
  "data": {
    "id":             42,
    "project_id":     1,
    "project_name":   "Outbound B2B DACH",
    "name":           "Max Mustermann",
    "company":        "Mustermann GmbH",
    "phone":          "+49 221 1234567",
    "email":          "max@mustermann-gmbh.de",
    "position":       "Geschaeftsfuehrer",
    "address":        "Musterstrasse 1, 50667 Koeln",
    "website":        "www.mustermann-gmbh.de",
    "status":         "Neu",
    "notes":          "Kontakt ueber Kontaktformular",
    "followup_at":    null,
    "pipeline_stage": "In Auftrag",
    "deal_value":     250.00,
    "created_at":     "2026-06-01 09:15:00",
    "updated_at":     "2026-06-01 09:15:00"
  }
}
404 Not Found
{
  "success": false,
  "error": {
    "code":    "not_found",
    "message": "Lead mit ID 9999 nicht gefunden."
  }
}
cURL
curl "https://api.revenue-lab.de/v1/leads/42" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"

Lead erstellen

Erstellt einen neuen Lead. Pflicht: project_id + mindestens ein Kontaktfeld (name, company, phone oder email).

POST https://api.revenue-lab.de/v1/leads Gibt 201 + das erstellte Lead-Objekt zurueck

Request Body

FeldTypBeschreibung
project_idintegerPflichtProjekt-ID (via GET /v1/projects abrufbar)
namestringOptional*Vor- und Nachname
companystringOptional*Firmenname
phonestringOptional*Telefonnummer (freies Format, z.B. +49 221 1234567)
emailstringOptional*E-Mail-Adresse
positionstringOptionalPosition / Jobtitel
addressstringOptionalAdresse
websitestringOptionalWebsite-URL
statusstringOptionalLead-Status (Standard: Neu). Siehe Status-Werte.
notesstringOptionalNotizen zum Lead
followup_atdatetimeOptionalWiedervorlage-Zeitpunkt (2026-06-10T14:00:00)
deal_valuenumberOptionalLead-Wert in Euro (Standard: Projekt-Standardwert)

* Mindestens eines von name, company, phone oder email muss angegeben werden.

cURL
curl -X POST "https://api.revenue-lab.de/v1/leads" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": 1,
    "name":       "Max Mustermann",
    "company":    "Mustermann GmbH",
    "phone":      "+49 221 1234567",
    "email":      "max@mustermann-gmbh.de",
    "position":   "Geschaeftsfuehrer",
    "notes":      "Anfrage ueber Kontaktformular, Interesse an Paket Business",
    "status":     "Neu"
  }'
Python
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://api.revenue-lab.de/v1"
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type":  "application/json"
}

lead = {
    "project_id": 1,
    "name":       "Max Mustermann",
    "company":    "Mustermann GmbH",
    "phone":      "+49 221 1234567",
    "email":      "max@mustermann-gmbh.de",
    "position":   "Geschaeftsfuehrer",
    "notes":      "Anfrage ueber Kontaktformular"
}

res = requests.post(f"{BASE}/leads", json=lead, headers=headers)
result = res.json()

if result["success"]:
    print(f"Lead erstellt: ID {result['data']['id']}")
else:
    print(f"Fehler: {result['error']['message']}")
JavaScript
const res = await fetch('https://api.revenue-lab.de/v1/leads', {
  method:  'POST',
  headers: {
    'Authorization': 'Bearer rl_live_DEIN_API_KEY',
    'Content-Type':  'application/json',
  },
  body: JSON.stringify({
    project_id: 1,
    name:       'Max Mustermann',
    company:    'Mustermann GmbH',
    phone:      '+49 221 1234567',
    email:      'max@mustermann-gmbh.de',
    notes:      'Anfrage ueber Kontaktformular',
  }),
});

const { success, data, error } = await res.json();
if (success) console.log('Lead erstellt:', data.id);
else console.error(error.message);
201 Created
{
  "success": true,
  "data": {
    "id":             42,
    "project_id":     1,
    "name":           "Max Mustermann",
    "company":        "Mustermann GmbH",
    "phone":          "+49 221 1234567",
    "email":          "max@mustermann-gmbh.de",
    "position":       "Geschaeftsfuehrer",
    "status":         "Neu",
    "pipeline_stage": "In Auftrag",
    "deal_value":     250.00,
    "created_at":     "2026-06-05 10:30:00",
    "updated_at":     "2026-06-05 10:30:00"
  }
}

Lead aktualisieren

Aktualisiert einzelne Felder eines Leads. Nur gesendete Felder werden geaendert — alles andere bleibt unberuehrt.

PATCH https://api.revenue-lab.de/v1/leads/{id} Gibt das aktualisierte Lead-Objekt zurueck

Aktualisierbare Felder

Alle Felder sind optional — sende nur was sich ändert. Unbekannte Keys werden silent ignoriert (kein Fehler).

Stammdaten

FeldTypBeschreibung
namestringVoller Name (Anzeige)
salutationstringAnrede (Herr / Frau / Divers)
first_namestringVorname
last_namestringNachname
companystringFirma
legal_formstringRechtsform (GmbH, UG, AG, ...)
positionstringPosition / Jobtitel
lead_typestringb2b oder b2c
phonestringTelefon
emailstringE-Mail
websitestringWebsite

Adresse

FeldTypBeschreibung
addressstringKomplette Adresse als String (Legacy — wenn gesetzt, werden Einzelfelder ignoriert)
streetstringStraße + Hausnummer
zipstringPostleitzahl
citystringOrt
countrystringLand

Sales-Pipeline & Deal

FeldTypBeschreibung
statusstringLead-Status. pipeline_stage wird automatisch synchronisiert (siehe Auto-Logik).
pipeline_stagestringPipeline-Stage explizit setzen. Unterstützt Custom-Stages. Bei is_won-Stages: Auto-Convert greift.
lost_reasonstring|nullVerlust-Grund (Freitext, nur sinnvoll bei status=Verloren)
deal_valuenumber|nullDeal-Wert in Euro
deal_probabilityint 0-100|nullAbschlusswahrscheinlichkeit in Prozent. Wird bei "Gewonnen" automatisch auf 100 gesetzt.
expected_close_atdate|nullVoraussichtliches Abschluss-Datum (YYYY-MM-DD). Bei "Gewonnen" und vorher leer: heute.
sourcestringLead-Quelle (Cold-Call, LinkedIn, Empfehlung, ...)
project_idint|nullAuftrag/Projekt-Zuordnung. Wird gegen Tenant validiert.

Termine & Datumsfelder

Alle Datetime-Felder akzeptieren: ISO-8601 (2026-07-01T09:00:00), MySQL-Format (2026-07-01 09:00:00), nur Datum (2026-07-01), null zum Entfernen, oder den Shortcut-String "NOW" (Server-Zeit).

FeldTypBeschreibung
followup_atdatetime|nullWiedervorlage-Datum (für Auto-Dialer-Queue)
termin_atdatetime|nullVereinbarter Termin mit dem Lead
appointment_set_atdatetime|nullZeitpunkt der Terminvereinbarung (für Reports)

Sonstiges

FeldTypBeschreibung
notesstring|nullNotiz-Feld (ersetzt den bestehenden Text. Für append: POST /v1/leads/{id}/notes).
customobjectCustom-Field-Werte als {"field_key": "value"}. Siehe Custom Fields.
💡
Status ↔ Pipeline-Stage Synchronisation:
Wenn du status setzt, wird pipeline_stage automatisch mitgesetzt: Bad DataBad Data, GewonnenGewonnen, VerlorenVerloren, alles andere→In Auftrag. Setzt du pipeline_stage auf eine is_won-Custom-Stage, wird auch status="Gewonnen" implizit gesetzt. Explizite Werte schlagen Auto-Logik immer.
🏆
Auto-Convert bei "Gewonnen": Wechselt der Lead in eine is_won-Stage, wird automatisch ein Kunde angelegt (idempotent), deal_probability=100 gesetzt und expected_close_at=heute (sofern leer). Details: Auto-Logik bei "Gewonnen".
cURL — Termin setzen
# Status auf "Terminiert" setzen + Wiedervorlage eintragen
curl -X PATCH "https://api.revenue-lab.de/v1/leads/42" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status":      "Terminiert",
    "notes":       "Termin am 10.06. um 14:00 bestaetigt",
    "followup_at": "2026-06-10T14:00:00",
    "termin_at":   "2026-06-10T14:00:00"
  }'

# Lead als "Gewonnen" markieren — löst Auto-Convert zu Kunde aus
# deal_probability=100 + expected_close_at=heute werden automatisch gesetzt
curl -X PATCH "https://api.revenue-lab.de/v1/leads/42" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status":     "Gewonnen",
    "deal_value": 12500,
    "custom": {
      "angebotspaket":  "LinkedIn Starter",
      "kampagnenstart": "2026-07-01",
      "laufzeit":       "9 Monate"
    }
  }'
Python
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://api.revenue-lab.de/v1"
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type":  "application/json"
}

lead_id = 42
update  = {
    "status":      "Terminiert",
    "notes":       "Termin am 10.06. um 14:00 bestaetigt",
    "followup_at": "2026-06-10T14:00:00"
}

res = requests.patch(f"{BASE}/leads/{lead_id}", json=update, headers=headers)
result = res.json()
print(f"Neuer Status: {result['data']['status']}")
200 OK — Won-Szenario
{
  "success": true,
  "data": {
    "id":                42,
    "name":              "Max Mustermann",
    "company":           "Mustermann GmbH",
    "status":            "Gewonnen",
    "pipeline_stage":    "Gewonnen",
    "deal_value":        "12500.00",
    "deal_probability":  100,           // auto-gesetzt
    "expected_close_at": "2026-06-06",  // auto-gesetzt (heute)
    "custom": {
      "angebotspaket":  "LinkedIn Starter",
      "kampagnenstart": "2026-07-01",
      "laufzeit":       "9 Monate"
    }
    // ...alle weiteren Felder
  },
  "kunde_id":     142,                  // neu angelegter Kunde
  "custom_saved": ["angebotspaket", "kampagnenstart", "laufzeit"]
}

Notizen

Notizen landen im Verlauf eines Leads (identisch mit dem Notizen-Feed im CRM-Drawer). Ideal fuer automatische Status-Updates aus Workflows.

Notiz hinzufuegen

POST https://api.revenue-lab.de/v1/leads/{id}/notes Gibt 201 zurueck
FeldTypBeschreibung
note string Pflicht Text der Notiz (max. 5000 Zeichen). Alias: text.
cURL
curl -X POST "https://api.revenue-lab.de/v1/leads/42/notes" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Angebot per Mail versendet. Rueckruf in 3 Tagen." }'
Python
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://api.revenue-lab.de/v1"
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type":  "application/json"
}

lead_id = 42
note    = {"note": "Angebot per Mail versendet. Rueckruf in 3 Tagen."}

res = requests.post(f"{BASE}/leads/{lead_id}/notes", json=note, headers=headers)
print(res.json())  # {"success": true, "data": {...}}
201 Created
{
  "success": true,
  "data": {
    "id": 187,
    "lead_id": 42,
    "note": "Angebot per Mail versendet. Rueckruf in 3 Tagen.",
    "created_at": "2026-06-05 14:30:00"
  }
}

Notizen-Verlauf abrufen

GET https://api.revenue-lab.de/v1/leads/{id}/notes Neueste zuerst, max. 100
200 OK
{
  "success": true,
  "data": [
    {
      "note": "Angebot per Mail versendet. Rueckruf in 3 Tagen.",
      "created_at": "2026-06-05 14:30:00",
      "user_name": null    // null = via API erstellt
    },
    {
      "note": "Erstanruf: Interesse an Paket Business, Angebot gewuenscht",
      "created_at": "2026-06-03 10:15:00",
      "user_name": "Tom Weber"   // von einem CRM-User erstellt
    }
  ],
  "total": 2
}

Lead → Kunde konvertieren

Erstellt aus einem Lead einen Kunden-Datensatz. Idempotent: existiert bereits ein Kunde fuer diesen Lead, wird dieser zurueckgegeben ("created": false).

POST https://api.revenue-lab.de/v1/leads/{id}/convert Gibt 201 + Kunden-Objekt zurueck
FeldTypBeschreibung
mark_won boolean Optional Setzt den Lead zusaetzlich auf Status Gewonnen. Default: true.
cURL
curl -X POST "https://api.revenue-lab.de/v1/leads/42/convert" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "mark_won": true }'
Python
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://api.revenue-lab.de/v1"
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type":  "application/json"
}

lead_id = 42
res = requests.post(
    f"{BASE}/leads/{lead_id}/convert",
    json={"mark_won": True},
    headers=headers
)
result = res.json()
if result["data"]["created"]:
    print(f"Neuer Kunde: ID {result['data']['kunde']['id']}")
else:
    print("Kunde existierte bereits")
201 Created
{
  "success": true,
  "data": {
    "created": true,
    "kunde": {
      "id":      12,
      "name":    "Max Mustermann",
      "firma":   "Mustermann GmbH",
      "lead_id": 42
    }
  }
}

Dateien hochladen

Haenge Dateien (PDF-Angebote, Vertraege, Screenshots, Sprachnotizen-Transkripte, ...) direkt an einen Lead. Die Datei landet im gleichen Dokumenten-Bereich wie ein manueller Upload im CRM-Drawer.

💾
Zwei Upload-Methoden:
1. multipart/form-data — klassisch wie ein HTML-Formular (z.B. curl -F file=@...)
2. application/json mit Base64 — ideal fuer n8n/Make/Zapier ohne Multipart-Support

Limits & erlaubte Typen

  • Max. Groesse: 20 MB pro Datei
  • Bilder: JPG, PNG, GIF, WebP
  • Dokumente: PDF, DOC(X), XLS(X), PPT(X), TXT, CSV
  • Archive: ZIP
  • Nicht erlaubt: SVG (XSS-Risk), EXE, JS

Optionale Felder: Upload als Vertrag

Mit dem optionalen Flag as_contract=1 landet die Datei zusätzlich im ✍️ Verträge-Tab des Leads (nicht nur unter "Dateien"). Ideal für PandaDoc/DocuSign-Workflows, bei denen die unterzeichnete PDF zurückkommt.

FeldTypDefaultBeschreibung
as_contractboolfalseWenn 1/true/yes: zusätzlicher Eintrag in contracts-Tabelle.
contract_titlestringFilename ohne EndungTitel im Verträge-Tab (z. B. "LinkedIn Premium 9M")
contract_recipient_namestringLead-NameEmpfänger-Anzeige im Verträge-Tab
contract_recipient_emailstringLead-E-MailEmpfänger-E-Mail im Verträge-Tab
contract_statusstringcompletedpending | completed | declined | expired. Bei completed: signed_at wird auf jetzt gesetzt.
🏆
Mit as_contract=1 + Setting "Auto-Won": Wenn Settings → Documenso → Lead auto auf Gewonnen aktiv ist, löst der Upload die komplette Won-Kaskade aus: Pipeline-Stage → Gewonnen, Kunde wird angelegt, deal_probability=100, expected_close_at=heute. Ein einziger HTTP-Call schließt den ganzen Deal ab.

Datei hochladen (Multipart)

POST https://api.revenue-lab.de/v1/leads/{id}/files Content-Type: multipart/form-data — Feld file
cURL — Multipart
curl -X POST "https://dashboard.revenue-lab.de/api?__path=leads/42/files" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -F "file=@/pfad/zu/angebot.pdf"
Python — Multipart
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://dashboard.revenue-lab.de/api"
headers = {"Authorization": f"Bearer {API_KEY}"}

lead_id = 42
with open("angebot.pdf", "rb") as f:
    res = requests.post(
        f"{BASE}?__path=leads/{lead_id}/files",
        headers=headers,
        files={"file": ("angebot.pdf", f, "application/pdf")}
    )

print(res.json())
# → {"success": true, "data": {"id": 17, "lead_id": 42, "orig_name": "angebot.pdf",
#                              "filesize": 234567, "download_url": "..."}}
JavaScript — FormData
const fd = new FormData();
fd.append('file', fileInput.files[0]);

const res = await fetch(
  'https://dashboard.revenue-lab.de/api?__path=leads/42/files',
  {
    method: 'POST',
    headers: { 'Authorization': 'Bearer rl_live_DEIN_API_KEY' },
    body: fd
  }
);
const { data } = await res.json();
console.log('Datei-ID:', data.id);
201 Created
{
  "success": true,
  "data": {
    "id":           17,
    "lead_id":      42,
    "orig_name":    "angebot.pdf",
    "mime_type":    "application/pdf",
    "filesize":     234567,
    "created_at":   "2026-06-05 21:00:00",
    "download_url": "https://api.revenue-lab.de/v1/leads/42/files/17/download"
  }
}

Datei hochladen (JSON + Base64 — fuer n8n/Make/Zapier)

Wenn deine Plattform kein Multipart kann, sende JSON mit filename + content_base64:

cURL — Base64
# Datei vorher in Base64 konvertieren
B64=$(base64 -w0 angebot.pdf)

curl -X POST "https://dashboard.revenue-lab.de/api?__path=leads/42/files" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"angebot.pdf\", \"content_base64\": \"$B64\"}"
Python — Base64
import requests, base64

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://dashboard.revenue-lab.de/api"
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type":  "application/json"
}

with open("angebot.pdf", "rb") as f:
    b64 = base64.b64encode(f.read()).decode("ascii")

payload = {"filename": "angebot.pdf", "content_base64": b64}
res = requests.post(f"{BASE}?__path=leads/42/files", json=payload, headers=headers)
print(res.json())
n8n — HTTP Request Node
# n8n HTTP Request Node Konfiguration

Method:          POST
URL:             https://dashboard.revenue-lab.de/api?__path=leads/{{$json.lead_id}}/files
Authentication:  Header Auth
  Header Name:   Authorization
  Header Value:  Bearer rl_live_DEIN_API_KEY
Send Body:       Yes
Body Type:       JSON
JSON Body:
{
  "filename": "{{$node['PandaDoc'].json['document_name']}}",
  "content_base64": "{{$node['PandaDoc'].json['pdf_base64']}}"
}

# Antwort: data.download_url kann direkt weiterverarbeitet werden

Dateien auflisten

GET https://api.revenue-lab.de/v1/leads/{id}/files Liste aller Dateien zu einem Lead (max. 200)
cURL
curl "https://dashboard.revenue-lab.de/api?__path=leads/42/files" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"
200 OK
{
  "success": true,
  "data": [
    {
      "id":           17,
      "orig_name":    "angebot.pdf",
      "mime_type":    "application/pdf",
      "filesize":     234567,
      "uploaded_by":  0,
      "created_at":   "2026-06-05 21:00:00",
      "download_url": "https://api.revenue-lab.de/v1/leads/42/files/17/download"
    }
  ],
  "total": 1
}

Datei downloaden

GET https://api.revenue-lab.de/v1/leads/{id}/files/{file_id}/download Liefert die Datei direkt als Binary mit Original-MIME-Type
cURL
curl "https://dashboard.revenue-lab.de/api?__path=leads/42/files/17/download" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -o angebot.pdf
🔒
Auth bleibt Pflicht: Auch der Download verlangt einen gueltigen Bearer-Token. Die download_url ist nicht oeffentlich teilbar — ohne Header gibt's 401.

Kunden auflisten

GET https://api.revenue-lab.de/v1/customers

Query-Parameter

ParameterTypBeschreibung
searchstringOptionalFreitextsuche in Name und Firma
limitintegerOptionalMax. Ergebnisse (1–200, Standard: 50)
offsetintegerOptionalStartposition (Standard: 0)
200 OK
{
  "success": true,
  "data": [
    {
      "id":         12,
      "name":       "Max Mustermann",
      "firma":      "Mustermann GmbH",
      "email":      "max@mustermann-gmbh.de",
      "telefon":    "+49 221 1234567",
      "lead_id":    42,
      "created_at": "2026-06-05 15:00:00"
    },
    {
      "id":         13,
      "name":       "Lisa Schmidt",
      "firma":      "Schmidt & Partner AG",
      "email":      "l.schmidt@schmidt-partner.de",
      "telefon":    "+49 30 9876543",
      "lead_id":    43,
      "created_at": "2026-06-04 16:30:00"
    }
  ],
  "total": 2,
  "limit": 50,
  "offset": 0
}
cURL
curl "https://api.revenue-lab.de/v1/customers?search=Mustermann" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"
Python
import requests

API_KEY = "rl_live_DEIN_API_KEY"
BASE    = "https://api.revenue-lab.de/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

res = requests.get(f"{BASE}/customers", headers=headers)
for k in res.json()["data"]:
    print(f"{k['id']}: {k['firma']} ({k['name']})")

Einzelnen Kunden abrufen

GET https://api.revenue-lab.de/v1/customers/{id}
cURL
curl "https://api.revenue-lab.de/v1/customers/12" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "id":         12,
    "name":       "Max Mustermann",
    "firma":      "Mustermann GmbH",
    "email":      "max@mustermann-gmbh.de",
    "telefon":    "+49 221 1234567",
    "lead_id":    42,
    "created_at": "2026-06-05 15:00:00"
  }
}

ToDos auflisten

Listet alle ToDo-Karten des ToDo-Boards. Optional filterbar nach Spalte, zugewiesenem User oder Erledigt-Status.

GET https://api.revenue-lab.de/v1/todos

Query-Parameter

ParameterTypBeschreibung
column_idintNur Karten dieser Spalte (Spalten-IDs via /board sichtbar).
assigned_user_idintNur Karten, die diesem User zugewiesen sind.
completed0|11 = nur Karten in der letzten Spalte (Erledigt). 0 = alle anderen.
limitintMax 200 (Default 50).
offsetintPagination-Offset.
cURL
curl "https://api.revenue-lab.de/v1/todos?completed=0&assigned_user_id=5" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"

Einzelne ToDo abrufen

GET https://api.revenue-lab.de/v1/todos/{id}
200 OK
{
  "success": true,
  "data": {
    "id":                   "todo_42",
    "card_id":              42,
    "title":                "Lead nachfassen",
    "description":          "E-Mail-Sequenz Step 3",
    "color_label":          "#4a8fff",
    "due_date":             "2026-06-15",
    "assigned_user_id":     5,
    "assigned_user_name":   "Tom Weber",
    "column_id":            3,
    "column_name":          "In Arbeit",
    "is_completed":         false,
    "position":             2,
    "created_at":           "2026-06-08 11:22:00"
  }
}

ToDo anlegen

Erstellt eine neue Karte auf dem ToDo-Board. Loest den Webhook todo.created aus.

POST https://api.revenue-lab.de/v1/todos

Body (JSON)

FeldTypBeschreibung
title *stringKarten-Titel. Pflicht.
descriptionstringBeschreibung / Notiz.
column_idintOptional — ohne Angabe landet die Karte in der ersten Spalte.
color_labelstringHex-Farbe der Karte (z.B. #4a8fff). Default Blau.
due_datestringFaelligkeit (YYYY-MM-DD).
assigned_user_idintUser-ID, dem die Karte zugewiesen wird.
cURL
curl -X POST "https://api.revenue-lab.de/v1/todos" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Vertrag XY pruefen",
    "description": "Bis Freitag durchsehen und freigeben",
    "due_date": "2026-06-12",
    "color_label": "#ffb86b",
    "assigned_user_id": 5
  }'
201 Created
{
  "success": true,
  "data": {
    "id":                "todo_127",
    "card_id":           127,
    "title":             "Vertrag XY pruefen",
    "column_id":         1,
    "column_name":       "Backlog",
    "assigned_user_id":  5,
    "is_completed":      false
  }
}

ToDo aktualisieren

Aendert Felder einer Karte. Loest todo.updated aus. Wechselt die Karte in die letzte (Erledigt-)Spalte, wird zusaetzlich todo.completed gefeuert.

PATCH https://api.revenue-lab.de/v1/todos/{id}

Alle Felder optional. Felder die nicht im Body stehen, bleiben unveraendert.

cURL — Karte als erledigt markieren
curl -X PATCH "https://api.revenue-lab.de/v1/todos/127" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "column_id": 5 }'
Erledigt-Logik: Die letzte Spalte deines Boards (hoechste position) gilt als »Erledigt«. Verschiebst du eine Karte dorthin — egal ob per Drag im Board, per PATCH /v1/todos/{id} oder per Automation — feuert RevenueLab den Webhook todo.completed.

ToDo loeschen

DELETE https://api.revenue-lab.de/v1/todos/{id}
cURL
curl -X DELETE "https://api.revenue-lab.de/v1/todos/127" \
  -H "Authorization: Bearer rl_live_DEIN_API_KEY"

Automationen — Outbound Webhooks

Die REST-API schreibt Daten in RevenueLab. Automationen senden Events heraus: Tritt ein Ereignis ein (z.B. Pipeline-Stage aendert sich), feuert RevenueLab einen POST-Request an deine Ziel-URL.

⚙️
Einrichtung: Automationen unter Settings → Automationen verwalten (nur Admin). Pro Regel: Name, Trigger-Ereignis, optionaler Stage-Filter, Ziel-URL. Mit dem Test-Button wird ein Beispiel-Payload sofort gesendet.

Trigger-Ereignisse

EventWannAction im Payload
lead.created Ein Lead wird angelegt (CRM oder API). created
pipeline.update Die Pipeline-Stage eines Leads aendert sich. Optional auf eine Ziel-Stage einschraenkbar (z.B. nur Gewonnen). updated
appointment.set Ein Termin wird gesetzt (termin_at) oder der Status wechselt auf Terminiert. updated
todo.created Eine neue ToDo-Karte wird angelegt (Board oder API). created
todo.updated Eine ToDo-Karte wird geaendert (Titel, Beschreibung, Faelligkeit, Zuweisung, Spalte). updated
todo.completed Eine ToDo-Karte wird in die letzte Spalte (Erledigt) verschoben — egal ob via Board-Drag oder API-PATCH. completed
Zustellung: Fire-and-forget mit 3 Sekunden Timeout. Antworte mit HTTP 2xx um Empfang zu bestaetigen. Kein automatischer Retry — ist dein Endpunkt offline, geht das Event verloren. Die UI wird nicht blockiert (Response wird vor dem Webhook abgeschlossen).

Webhook-Payload

Jeder Webhook sendet ein einheitliches Envelope (Close-Stil). Geaenderte Felder werden als changed_fields mitgeliefert, inklusive vorheriger Werte in previous_data. Custom Fields liegen unter data.custom.*.

JSON — Beispiel: pipeline.update
{
  "subscription_id": "whsub_3",
  "tenant_id": 1,
  "event": {
    "id":             "ev_k7Bm3xQ9pLrT2vN8cZ1a4",
    "date_created":   "2026-06-05T14:22:00+02:00",
    "object_type":    "lead",
    "object_id":      "lead_42",
    "lead_id":        42,
    "action":         "updated",
    "trigger":        "pipeline.update",
    "changed_fields": ["status", "pipeline_stage"],
    "data": {
      "id":             42,
      "name":           "Max Mustermann",
      "company":        "Mustermann GmbH",
      "email":          "max@mustermann-gmbh.de",
      "phone":          "+49 221 1234567",
      "status":         "Gewonnen",
      "pipeline_stage": "Gewonnen",
      "deal_value":     "250.00",
      "termin_at":      "2026-06-10 14:00:00",
      "project_name":   "Outbound B2B DACH",
      "custom": {
        "branche":     "IT-Dienstleistung",
        "mitarbeiter": "25"
      }
    },
    "previous_data": {
      "status":         "Terminiert",
      "pipeline_stage": "Terminiert"
    },
    "actor": {
      "id":   7,
      "name": "Tom Weber"
    }
  }
}

Envelope-Felder

FeldTypBeschreibung
subscription_idstringID der Automation. Prefix: whsub_.
tenant_idintegerDein Account / Mandant.
event.idstringEindeutige Event-ID, Prefix ev_. Nutzbar zur Deduplizierung / Idempotenz.
event.date_createdstringISO-8601 Timestamp mit Zeitzone.
event.object_typestringAktuell immer "lead".
event.object_idstringSprechende ID, z.B. "lead_42". Numerisch auch als event.lead_id.
event.actionstring"created" oder "updated".
event.triggerstringDas ausloesende Ereignis: lead.created, pipeline.update, appointment.set.
event.changed_fieldsstring[]Liste der geaenderten Feldnamen. Bei created: alle befuellten Felder.
event.dataobjectVollstaendiger aktueller Lead-Zustand. Custom Fields unter data.custom.<key>.
event.previous_dataobjectAlte Werte der geaenderten Felder (Diff). Bei created leer: {}.
event.actorobjectAusloesender Nutzer mit id und name.
💡
n8n / Make Tipp: Filtere auf event.changed_fields (z.B. enthaelt "pipeline_stage"), lies den neuen Wert aus event.data.pipeline_stage und den alten aus event.previous_data.pipeline_stage. So reagierst du gezielt auf bestimmte Aenderungen.

Event-Log

Protokoll aller eingehenden API-Calls und ausgehenden Webhook-Zustellungen. Gleiche Daten wie unter Settings → Event-Log.

GET https://api.revenue-lab.de/v1/events

Query-Parameter

ParameterTypBeschreibung
categorystringapi (eingehende Calls) oder webhook (ausgehende Zustellungen). Leer = beides.
limitintegerMax. Ergebnisse (1–200, Standard: 50)
offsetintegerStartposition (Standard: 0)
200 OK
{
  "success": true,
  "data": [
    {
      "id":          501,
      "category":    "api",
      "method":      "POST",
      "route":       "/v1/leads",
      "object_type": "lead",
      "object_id":   "42",
      "status_code": 201,
      "success":     true,
      "actor":       "API Key: rl_live_abc1...",
      "ip":          "203.0.113.42",
      "created_at":  "2026-06-05 10:30:00"
    },
    {
      "id":          502,
      "category":    "webhook",
      "method":      "lead.created",
      "route":       "https://hook.eu2.make.com/abc123...",
      "object_type": "lead",
      "object_id":   "42",
      "status_code": 200,
      "success":     true,
      "actor":       "Tom Weber",
      "meta":        { "changed_fields": ["name", "company", "phone"] },
      "created_at":  "2026-06-05 10:30:01"
    }
  ],
  "total": 502,
  "limit": 50,
  "offset": 0
}

Wie API-Calls funktionieren

Diese Sektion erklärt das mentale Modell der API — gut zu lesen bevor du den ersten Workflow baust. Spart später viel Debugging.

1. Lebenszyklus eines Leads

Ein Lead durchläuft in RevenueLab folgende Stufen — jede davon kannst du per API steuern:

PhaseWer triggertAPI-Call
ErstellungExterne Quelle (Website-Formular, Werbe-Lead, Import)POST /v1/leads
Kontakt & PflegeAgent im Dialer + externe Tools (E-Mail-Antwort, SMS)PATCH /v1/leads/{id} + POST /v1/leads/{id}/notes
Angebot/VertragVertragstool (PandaDoc, Documenso) via n8nPOST /v1/leads/{id}/files mit as_contract=1
AbschlussVertragstool / Sales-AgentPATCH /v1/leads/{id} mit status=GewonnenAuto-Logik feuert
KundenpflegeCRM-Team (eigene Kunden-API, oder via Customer-ID)GET /v1/customers/{id}

2. Der "PATCH-Geist": send only what changes

Anders als REST-PUT überschreibt PATCH den Lead nicht mit dem Body. Es ändert nur die Felder, die du explizit mitschickst. Felder die nicht im Body sind, bleiben unangetastet.

⚠️
Häufiger Anfänger-Fehler: Bei PATCH alle Felder mitsenden "weil man sie hat". Resultat: du überschreibst Felder die ein Sales-Agent gerade manuell geändert hat. Schick nur das, was du wirklich ändern willst. null = bewusst leer setzen. Feld weglassen = unverändert lassen.

3. Idempotenz — ein API-Call darf doppelt passieren

Webhook-Provider (n8n, Make, Zapier) wiederholen Calls bei Timeouts. Die wichtigsten RevenueLab-Endpoints sind darauf vorbereitet:

  • POST /v1/leads/{id}/convert — legt den Kunden nur an wenn noch keiner existiert, sonst gibt es den bestehenden zurück (created: false).
  • PATCH /v1/leads/{id} mit status=Gewonnen — Auto-Convert prüft ob schon ein Kunde da ist und legt nur fehlende an.
  • POST /v1/leads/{id}/files mit as_contract=1 — jeder Upload erzeugt einen eigenen Datei- und Vertrags-Eintrag (kein Dedup auf Filename), das ist gewollt.

4. Fire-and-Forget bei Webhooks

Wenn ein PATCH einen Outbound-Webhook auslöst (pipeline.update, appointment.set), wartet die API nicht auf die Webhook-Zustellung. Du bekommst die 200 OK-Antwort sofort, der Webhook geht im Hintergrund raus (fastcgi_finish_request). Vorteil: keine Verzögerung in deinem Workflow. Nachteil: wenn der Empfänger 500 zurückgibt, siehst du das nur im Event-Log (category=webhook).

5. Was du beobachtest, ist nicht die Wahrheit

Der Webhook-Payload enthält previous_data (was vorher war) und changed_fields (was sich geändert hat). Wenn dein Workflow auf "Lead wurde Gewonnen" reagieren soll, prüfe "pipeline_stage" in changed_fields && data.pipeline_stage === "Gewonnen" — nicht nur data.pipeline_stage === "Gewonnen". Sonst feuerst du bei jedem Update eines Won-Leads erneut.

Auto-Logik bei "Gewonnen"

Sobald ein Lead in eine is_won-Pipeline-Stage geht (standardmäßig Gewonnen, plus jede Custom-Stage mit is_won=1), feuert RevenueLab eine Kaskade von Auto-Aktionen. Das gilt für jeden Weg dorthin: UI, PATCH, File-Upload, oder Documenso-Webhook.

Was automatisch passiert

#AktionBedingung
1Kunde wird angelegt (Tabelle kunden) — mit Name, Firma, Telefon, E-Mail, Website, Adresse und Umsatz (aus deal_value).Nur wenn noch kein Kunde mit diesem lead_id existiert (idempotent).
2Kunden-Aktivität "system" angelegt — sichtbar im Kunden-Drawer.Immer, wenn (1) feuert.
3status wird auf Gewonnen gesetzt (auch bei Custom-is_won-Stages).Nur wenn Client nicht explizit anderen Status mitgab.
4deal_probability wird auf 100 gesetzt.Nur wenn Client keinen expliziten Wert mitgab. Überschreibt bestehende Werte (z. B. 70% → 100), denn "Gewonnen mit 70%" ist semantisch sinnlos.
5expected_close_at wird auf heute gesetzt.Nur wenn Client keinen expliziten Wert mitgab UND DB-Wert vorher leer war. Überschreibt KEINE bestehenden Werte — ein gesetztes Forecast-Datum bleibt.
6Audit-Eintrag wird angelegt (vertrag_unterzeichnet bei Datei-Upload als Vertrag, sonst pipeline_stage-Änderung).Immer.
7Outbound-Webhook pipeline.update wird gefeuert (Close-Style mit changed_fields + previous_data).Wenn aktive Automation für diesen Trigger existiert.

Begründung der Asymmetrie zwischen Punkt 4 und 5

Wahrscheinlichkeit ist axiomatisch — ein gewonnener Lead hat per Definition 100%. "Gewonnen mit 70%" widerspricht sich selbst, deshalb wird der Wert überschrieben.

Close-Date dagegen kann legitim in der Zukunft liegen ("Vertrag tritt am 01.09.2026 in Kraft" / "Kampagnenstart erst nächsten Monat"). Wenn Sales das bewusst gesetzt hat, würden wir Daten zerstören, indem wir auf "heute" überschreiben. Darum: nur befüllen wenn leer.

Was du explizit überschreiben kannst

Jeder der oben automatisierten Werte lässt sich vom Client immer explizit setzen — explizite Werte schlagen Auto-Defaults:

Override-Beispiel
// Beispiel: Lead gewinnen, aber mit nur 90% Wahrscheinlichkeit
// (z.B. weil Anzahlung noch offen ist) und Close-Date in der Zukunft
{
  "status":            "Gewonnen",
  "deal_probability":  90,
  "expected_close_at": "2026-09-01"
}

Die drei Wege zu "Gewonnen"

WegCallWann nutzen
PATCH statusPATCH /v1/leads/{id}
{ "status": "Gewonnen" }
Einfachster Weg. Wenn dein Workflow eh PATCH-Felder updatet (Deal-Wert, Notiz, Custom-Felder).
PATCH pipeline_stagePATCH /v1/leads/{id}
{ "pipeline_stage": "Premium-Won" }
Wenn du eine Custom-Stage mit is_won=1 nutzt (z. B. um nach Deal-Größe zu segmentieren).
File-Upload as_contractPOST /v1/leads/{id}/files
multipart mit as_contract=1
Wenn du eine unterzeichnete Vertrags-PDF hochlädst (PandaDoc, DocuSign). Datei landet im Verträge-Tab UND Lead geht auf Gewonnen, sofern auto_won_on_contract_signed aktiv ist.
⚙️
Wo das Toggle sitzt: Settings → ✍️ Documenso → "Lead automatisch auf Gewonnen wenn Vertrag unterzeichnet". Steuert ob File-Upload as_contract=1 die ganze Won-Kaskade triggert oder nur den Vertrag ablegt.

Custom Fields

Custom Fields ("Benutzerdefinierte Felder") sind tenant-spezifische Zusatzfelder, die du im Admin-Bereich definierst (Name, Typ, Sichtbarkeitsbereich). RevenueLab speichert sie getrennt von der leads-Tabelle in custom_field_values — du musst sie aber API-seitig genauso behandeln wie normale Felder.

Lesen: bei jedem Lead-Read enthalten

Sowohl GET /v1/leads/{id} als auch der Webhook-Payload (data.custom) enthalten alle Custom-Field-Werte als { field_key: value }:

Auszug Lead-Response
{
  "data": {
    "id":    4426,
    "name":  "Sabine Jäger",
    // ... Standard-Felder ...
    "custom": {
      "angebotspaket":        "LinkedIn Starter",
      "garantiertetermine":   "5",
      "kampagnenstart":       "2026-07-01",
      "laufzeit":             "9 Monate",
      "preisnetto":           "12500"
    }
  }
}

Schreiben: custom-Objekt im PATCH-Body

Im PATCH-Body fügst du ein custom-Objekt hinzu. Die Keys sind die field_key-Werte (genauso wie im Read-Payload). Werte: String/Number/Boolean. null löscht den Wert.

PATCH mit Custom-Feldern
PATCH /v1/leads/4426
{
  "deal_value": 12500,
  "custom": {
    "angebotspaket":  "LinkedIn Premium",
    "kampagnenstart": "2026-07-15",
    "preisnetto":     "18500"
  }
}

Die Response enthält custom_saved: [...] mit der Liste der tatsächlich gespeicherten Keys — perfekt zum Debugging:

200 OK
{
  "success": true,
  "data": { /* ... inkl. custom-Objekt mit neuen Werten ... */ },
  "custom_saved": ["angebotspaket", "kampagnenstart", "preisnetto"]
}
🔎
Unbekannte Keys werden silent ignoriert. Schickst du "angbotspaket" (Tippfehler) statt "angebotspaket", erscheint der Key NICHT in custom_saved — aber es gibt auch keinen Fehler. Das ist gewollt (Tenants können Felder löschen, ohne dass alte Integrationen brechen), erfordert aber dass du custom_saved in deinem Workflow prüfst wenn du sicher gehen willst.

Wo finde ich die field_keys?

  • Admin → Custom Fields → Spalte "Key"
  • Oder: einmal GET /v1/leads/{id} aufrufen — im custom-Objekt siehst du alle Keys
  • Oder: einen Webhook für lead.created oder pipeline.update anlegen — im Payload steht event.data.custom mit allen Keys

Lead-Status-Werte

Erlaubte Werte fuer das Feld status bei POST /v1/leads und PATCH /v1/leads/{id}:

StatusBeschreibungPipeline-StageIn Dialer-Queue?
NeuNeuer Lead, noch nicht kontaktiert (Standard)In AuftragJa
WiedervorlageRueckruf geplant — erscheint zum Wiedervorlage-ZeitpunktIn AuftragJa
Nicht erreichtAnrufversuch ohne ErreichungIn AuftragJa
Entscheider nicht erreichtNur Sekretariat/Assistent erreichtIn AuftragJa
TerminiertTermin wurde vereinbartIn AuftragNein
Kein InteresseLead hat abgelehntIn AuftragNein
Bad DataFehlerhafte KontaktdatenBad DataNein
Info Mail angefordertInfomaterial angefordertIn AuftragNein
Info Mail versendetInfomaterial wurde versendetIn AuftragNein
GewonnenDeal abgeschlossen — löst Auto-Convert-Kaskade ausGewonnenNein
VerlorenDeal verloren (Grund optional via lost_reason)VerlorenNein

Lead-Objekt (vollstaendiges Schema)

Jedes Lead-Objekt in data enthaelt diese Felder:

FeldTypBeschreibung
idintegerEindeutige Lead-ID
project_idintegerZugehoeriges Projekt
project_namestringProjektname (read-only, JOIN)
namestring|nullVor- und Nachname
salutation / first_name / last_namestring|nullAufgespaltene Namen
companystring|nullFirmenname
legal_formstring|nullRechtsform
positionstring|nullPosition / Jobtitel
lead_typestringb2b oder b2c
phonestring|nullTelefonnummer
emailstring|nullE-Mail-Adresse
websitestring|nullWebsite-URL
addressstring|nullAdresse als ganzer String (Legacy)
street / zip / city / countrystring|nullAufgespaltene Adressfelder
statusstringLead-Status (siehe Status-Werte)
pipeline_stagestringAktuelle Pipeline-Stage (Standard oder Custom)
lost_reasonstring|nullVerlust-Grund (nur bei status=Verloren)
sourcestring|nullLead-Quelle
deal_valuenumber|nullDeal-Wert in Euro
deal_probabilityint|nullAbschlusswahrscheinlichkeit (0–100). Bei "Gewonnen" auto auf 100.
expected_close_atdate|nullVoraussichtl. Abschluss-Datum (YYYY-MM-DD)
termin_atdatetime|nullVereinbarter Termin mit dem Lead
appointment_set_atdatetime|nullZeitpunkt der Terminvereinbarung
followup_atdatetime|nullWiedervorlage-Zeitpunkt
notesstring|nullNotizen-Text (Single-Field — für Verlauf: /v1/leads/{id}/notes)
customobjectCustom-Field-Werte (siehe Custom Fields)
created_atdatetimeErstellungszeitpunkt
updated_atdatetimeLetzter Aenderungszeitpunkt

Zapier, Make & n8n Integration

n8n — HTTP Request Node

  1. Node: HTTP Request
  2. Method: POST
  3. URL: https://api.revenue-lab.de/v1/leads
  4. Authentication: Header Auth
  5. Header Name: Authorization — Value: Bearer rl_live_DEIN_API_KEY
  6. Body Type: JSON
  7. Body: Felder mappen (project_id, name, phone, email, ...)

n8n — Webhook empfangen

  1. Node: Webhook (Trigger)
  2. HTTP Method: POST
  3. Webhook-URL kopieren und in Settings → Automationen als Ziel-URL eintragen
  4. Daten liegen unter body.event.data.* und body.event.changed_fields

Make (Integromat) — HTTP-Modul

  1. Modul: HTTP → Make a request
  2. URL: https://api.revenue-lab.de/v1/leads
  3. Method: POST
  4. Headers: AuthorizationBearer rl_live_DEIN_API_KEY
  5. Body type: Rawapplication/json

Zapier — Webhooks by Zapier

  1. Trigger: Beliebig (Typeform, Jotform, Google Forms, ...)
  2. Action: Webhooks by Zapier → POST
  3. URL: https://api.revenue-lab.de/v1/leads
  4. Payload Type: json
  5. Headers: Authorization | Bearer rl_live_DEIN_API_KEY
JSON — Zapier / Make / n8n Data Mapping
{
  "project_id": 1,
  "name":       "{{Name}}",
  "email":      "{{Email}}",
  "phone":      "{{Telefon}}",
  "company":    "{{Firma}}",
  "notes":      "Quelle: {{Formularname}}"
}
💡
Projekt-ID herausfinden: Einmalig GET /v1/projects aufrufen, die gewuenschte ID notieren und im Workflow fest hinterlegen.