{"openapi":"3.1.0","info":{"title":"Pulse API","version":"1.0.0","summary":"Schnittstelle, mit der andere Systeme Daten an Pulse senden und aus Pulse lesen.","description":"Mit der Pulse API verbinden Betriebe andere Systeme mit Pulse, zum Beispiel die eigene Website, einen Anfrage-Funnel oder ein Automatisierungs-Tool.\n\n## Authentifizierung\nJede Anfrage braucht einen API-Schlüssel. Ein Admin legt ihn in Pulse unter **Einstellungen → Integrationen → API** an. Der Schlüssel wird nur einmal angezeigt und gehört zu genau einem Betrieb.\n\nSende ihn im Header `Authorization: Bearer <API-Schlüssel>` (alternativ `X-API-Key: <API-Schlüssel>`).\n\n**Nur vom Server aus aufrufen.** Der Schlüssel ist geheim und darf nie im Browser, in einer App oder in öffentlichem Code stehen. Ein Website-Formular sendet seine Daten an den eigenen Server; erst dieser ruft die Pulse API auf.\n\n## Rechte\nJeder Schlüssel hat Rechte (Scopes), zum Beispiel `leads:write`. Fehlt ein Recht, antwortet die API mit `403 missing_scope`. Ist das zugehörige Modul im Betrieb nicht aktiv, mit `403 module_disabled`.\n\n## Fehler\nFehler haben immer dieselbe Form: `{ \"error\": \"<code>\", \"message\": \"<Text>\" }`. `error` ist ein fester Code für Programme, `message` ein deutscher Text für Menschen.\n\n## Limits\nHöchstens 60 Aufrufe pro Minute und Schlüssel, Anfragen höchstens 64 KB. Darüber antwortet die API mit `429 rate_limited` bzw. `413 payload_too_large`.\n\n## Doppelte Anfragen\nSchreibende Aufrufe akzeptieren einen `Idempotency-Key`-Header. Wird dieselbe Anfrage mit demselben Schlüssel erneut gesendet (etwa nach einem Netzwerkfehler), legt Pulse nichts doppelt an und antwortet mit dem bestehenden Datensatz.\n\n## Versionen\nDie Version steht in der Adresse (`/v1`). Innerhalb einer Version kommen nur neue Bereiche, Felder und Fehlercodes dazu; bestehende ändern sich nicht. Ignoriere unbekannte Felder in Antworten.\n\n## Protokoll\nJeder Aufruf mit gültigem Schlüssel erscheint 30 Tage lang im Protokoll unter Einstellungen → Integrationen → API.","contact":{"name":"Pulse","url":"https://pulse-suite.com"}},"servers":[{"url":"https://app.pulse-suite.com/api/public/v1","description":"Pulse"}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"tags":[{"name":"Allgemein","description":"Schlüssel und Verbindung prüfen."},{"name":"Leads","description":"Anfragen als Leads in die Pipeline des Betriebs senden. Recht: `leads:write`."}],"paths":{"/me":{"get":{"tags":["Allgemein"],"operationId":"getMe","summary":"Schlüssel prüfen","description":"Zeigt, zu welchem Betrieb der Schlüssel gehört und welche Rechte er hat. Ideal, um eine neue Anbindung zu testen.","responses":{"200":{"description":"Schlüssel ist gültig.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"},"example":{"tenant":"Muster Bau GmbH","key":"Website","scopes":["leads:write"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/leads":{"post":{"tags":["Leads"],"operationId":"createLead","summary":"Lead anlegen","description":"Legt einen neuen Lead in der ersten Stufe der Lead-Pipeline an, zum Beispiel aus einem Website-Formular oder Anfrage-Funnel.\n\n- Es braucht mindestens `company_name`, `first_name`, `last_name` oder `email`.\n- Beliebige Funnel-Fragen gehen in `answers` und erscheinen im Lead unter «Anfrage-Details».\n- Der neue Lead löst in Pulse das Ereignis «Lead erstellt» aus; darauf können Automationen reagieren, etwa eine Benachrichtigung.\n- Mit `Idempotency-Key` (oder `external_id`) entsteht bei wiederholtem Senden nur ein Lead.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Eindeutige Kennung dieser Anfrage (max. 200 Zeichen), zum Beispiel eine UUID je Formular-Absendung. Gleicher Wert = gleicher Lead.","schema":{"type":"string","maxLength":200},"example":"3f1c2e0a-6a6b-4e0e-9a43-6a7c1d2b9e11"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreate"},"examples":{"funnel":{"summary":"Anfrage aus einem Website-Funnel","value":{"first_name":"Anna","last_name":"Beispiel","email":"anna@example.com","phone":"+49 681 123456","postal_code":"66111","city":"Saarbrücken","message":"Bitte rufen Sie mich am Vormittag an.","answers":[{"key":"project","question":"Was möchten Sie planen?","answer":"Einfamilienhaus"},{"key":"area","question":"Fläche in m²","answer":120},{"key":"visit","question":"Beratung vor Ort?","answer":true},{"key":"extras","question":"Extras","answer":["Garage","Keller"]}],"utm":{"source":"google","medium":"cpc","campaign":"beratung"},"page_url":"https://example.com/beratung","consents":{"privacy":{"accepted":true,"text":"Ich habe die Datenschutzerklärung gelesen."},"marketing":{"accepted":false}}}},"minimal":{"summary":"Nur E-Mail","value":{"email":"anna@example.com"}}}}}},"responses":{"200":{"description":"Diese Anfrage (gleicher `Idempotency-Key`) wurde bereits verarbeitet; es wurde nichts Neues angelegt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreated"},"example":{"id":"6b0b8a8e-3c1f-4c55-9a8e-2f1d6c3b7a10","duplicate":true}}}},"201":{"description":"Lead angelegt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreated"},"example":{"id":"6b0b8a8e-3c1f-4c55-9a8e-2f1d6c3b7a10","duplicate":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API-Schlüssel aus Einstellungen → Integrationen → API, z. B. `Authorization: Bearer pulse_…`."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Alternative zum Bearer-Header."}},"schemas":{"Me":{"type":"object","required":["tenant","key","scopes"],"properties":{"tenant":{"type":"string","description":"Name des Betriebs, zu dem der Schlüssel gehört."},"key":{"type":"string","description":"Name des Schlüssels."},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}}}},"Scope":{"type":"string","enum":["leads:write"],"description":"`leads:write` – Leads anlegen. Weitere Rechte kommen mit neuen Bereichen dazu."},"LeadCreate":{"type":"object","description":"Mindestens eines der Felder `company_name`, `first_name`, `last_name` oder `email` ist nötig. Leere Texte gelten als nicht angegeben; zu lange Texte werden gekürzt. Unbekannte Felder werden ignoriert.","properties":{"company_name":{"type":"string","maxLength":200,"description":"Firma."},"first_name":{"type":"string","maxLength":100},"last_name":{"type":"string","maxLength":100},"email":{"type":"string","format":"email","maxLength":254,"description":"Wird kleingeschrieben gespeichert."},"phone":{"type":"string","maxLength":50},"street":{"type":"string","maxLength":200},"house_number":{"type":"string","maxLength":20},"postal_code":{"type":"string","maxLength":20},"city":{"type":"string","maxLength":100},"country":{"type":"string","maxLength":100},"website":{"type":"string","maxLength":300},"message":{"type":"string","maxLength":5000,"description":"Freitext der Anfrage. Erscheint in der Notiz des Leads."},"answers":{"description":"Antworten aus einem Funnel oder Formular, angezeigt im Lead unter «Anfrage-Details». Entweder eine Liste (Reihenfolge bleibt erhalten) oder ein Objekt `{ \"Frage\": Antwort }`. Höchstens 50 Antworten.","oneOf":[{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/Answer"}},{"type":"object","maxProperties":50,"additionalProperties":{"$ref":"#/components/schemas/AnswerValue"}}]},"utm":{"$ref":"#/components/schemas/Utm"},"gclid":{"type":"string","maxLength":200,"description":"Google-Ads-Klick-ID."},"fbclid":{"type":"string","maxLength":200,"description":"Meta-Klick-ID."},"page_url":{"type":"string","format":"uri","maxLength":500,"description":"Seite, auf der die Anfrage abgeschickt wurde."},"referrer":{"type":"string","format":"uri","maxLength":500,"description":"Seite, von der die Person gekommen ist."},"campaign_id":{"type":"string","format":"uuid","description":"ID einer Kampagne aus Pulse (Marketing). Unbekannte IDs werden ignoriert."},"consents":{"$ref":"#/components/schemas/Consents"},"external_id":{"type":"string","maxLength":200,"description":"Alternative zum Header `Idempotency-Key`."}},"anyOf":[{"required":["company_name"]},{"required":["first_name"]},{"required":["last_name"]},{"required":["email"]}]},"Answer":{"type":"object","required":["question"],"properties":{"key":{"type":"string","maxLength":100,"description":"Optionale technische Kennung der Frage, z. B. `area`."},"question":{"type":"string","minLength":1,"maxLength":200,"description":"Frage, wie sie angezeigt wurde."},"answer":{"$ref":"#/components/schemas/AnswerValue"}}},"AnswerValue":{"description":"Text, Zahl, Ja/Nein oder Liste von Texten/Zahlen. Höchstens 4000 Zeichen.","oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]}},{"type":"null"}]},"Utm":{"type":"object","description":"Kampagnen-Parameter aus der Adresse der Website. `source` wird zusätzlich als Kanal des Leads gespeichert.","properties":{"source":{"type":"string","maxLength":200},"medium":{"type":"string","maxLength":200},"campaign":{"type":"string","maxLength":200},"term":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":200}}},"Consent":{"type":"object","required":["accepted"],"properties":{"accepted":{"type":"boolean","description":"Hat die Person zugestimmt?"},"text":{"type":"string","maxLength":4000,"description":"Genau der Einwilligungstext, der angezeigt wurde. Pulse speichert ihn als Nachweis."}}},"Consents":{"type":"object","properties":{"privacy":{"$ref":"#/components/schemas/Consent","description":"Zustimmung zur Datenschutzerklärung."},"marketing":{"$ref":"#/components/schemas/Consent","description":"Freiwillige Einwilligung in Werbung (z. B. Newsletter). Nur bei `accepted: true` wird der Lead für Marketing freigegeben."}}},"LeadCreated":{"type":"object","required":["id","duplicate"],"properties":{"id":{"type":"string","format":"uuid","description":"ID des Leads in Pulse."},"duplicate":{"type":"boolean","description":"`true`, wenn diese Anfrage schon einmal verarbeitet wurde."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Fester Fehlercode."},"message":{"type":"string","description":"Beschreibung auf Deutsch."}}}},"responses":{"BadRequest":{"description":"`invalid_json`: Der Inhalt ist kein gültiges JSON. `invalid_body`: Der Inhalt ist kein JSON-Objekt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_json","message":"Der Inhalt ist kein gültiges JSON."}}}},"Unauthorized":{"description":"`unauthorized`: Schlüssel fehlt oder ist ungültig. `key_revoked`: Schlüssel wurde gesperrt. `key_expired`: Schlüssel ist abgelaufen.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"unauthorized","message":"API-Schlüssel fehlt oder ist ungültig."}}}},"Forbidden":{"description":"`missing_scope`: Dem Schlüssel fehlt das nötige Recht. `module_disabled`: Das Modul ist im Betrieb nicht aktiv. `tenant_inactive`: Der Betrieb ist nicht aktiv.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"missing_scope","message":"Dem API-Schlüssel fehlt das Recht leads:write."}}}},"PayloadTooLarge":{"description":"`payload_too_large`: Die Anfrage ist grösser als 64 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnsupportedMediaType":{"description":"`unsupported_media_type`: Der Inhalt muss als JSON (`Content-Type: application/json`) gesendet werden.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unprocessable":{"description":"Die Daten sind unvollständig oder ungültig.\n\n- `missing_contact`: Es fehlen Firma, Name und E-Mail.\n- `invalid_email`: Die E-Mail-Adresse ist ungültig.\n- `invalid_answers`: `answers` hat eine falsche Form, z. B. eine Antwort ohne `question`.\n- `too_many_answers`: Mehr als 50 Antworten.\n- `no_lead_stage`: Im Betrieb ist keine Lead-Stufe eingerichtet.\n- `rejected`: Pulse hat den Lead abgelehnt, z. B. weil das Limit der Testphase erreicht ist; `message` nennt den Grund.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_email","message":"Die E-Mail-Adresse ist ungültig."}}}},"RateLimited":{"description":"`rate_limited`: Mehr als 60 Aufrufe pro Minute mit diesem Schlüssel. Kurz warten und erneut senden.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalError":{"description":"`internal_error`: Unerwarteter Fehler in Pulse. Später erneut senden – mit demselben `Idempotency-Key`, dann entsteht nichts doppelt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}