20. API
PRONTO hat keine allgemeine REST-API mit API-Keys für freien CRM-Datenzugriff (siehe
21-api-keys.md). Was existiert, sind öffentliche, unauthentifizierte
Endpunkte, die jeweils eine einzelne einbettbare Funktion abbilden: Buchungsseiten,
Formulare, Newsletter-Anmeldung und das Chat-Widget. Jeder Endpunkt ist auf die Daten des
über workspace/kundeId referenzierten Kunden beschränkt (Tenant-Isolation, siehe
25-security.md) — es gibt keine globale Auflistung fremder Daten.
Basis-URL in allen Beispielen: https://app.pronto.example (durch die eigene PRONTO-Domain
ersetzen).
Buchungsseiten
GET /api/public/kalender/info
Lädt Name, Dauer, Ort und Branding einer öffentlichen Buchungsseite.
- Methode:
GET - Authentication: keine (öffentlich)
- Query-Parameter:
workspace(Kunden-ID, Pflicht),calendar(Slug, Pflicht) - Response
200:{ "status": "ok", "kalender": { "id": "…", "name": "Erstgespräch", "beschreibung": "…", "dauerMinuten": 30, "ortTyp": "google_meet", "ortWert": null, "ortOptionen": [{ "typ": "google_meet", "wert": null }], "zeitzone": "Europe/Berlin", "farbe": "#4f46e5" }, "firmenname": "Musterfirma GmbH" } - Fehler:
400fehlende Parameter ·404 { "status": "nicht_gefunden" }
cURL
curl "https://app.pronto.example/api/public/kalender/info?workspace=WORKSPACE_ID&calendar=erstgespraech"
JavaScript
const res = await fetch(
"https://app.pronto.example/api/public/kalender/info?workspace=WORKSPACE_ID&calendar=erstgespraech",
);
const data = await res.json();
PHP
<?php
$url = "https://app.pronto.example/api/public/kalender/info?workspace=WORKSPACE_ID&calendar=erstgespraech";
$data = json_decode(file_get_contents($url), true);
Python
import requests
r = requests.get(
"https://app.pronto.example/api/public/kalender/info",
params={"workspace": "WORKSPACE_ID", "calendar": "erstgespraech"},
)
data = r.json()
GET /api/public/kalender/slots
Freie Zeitfenster in einem Zeitraum.
- Methode:
GET - Authentication: keine
- Query-Parameter:
kalenderId(Pflicht, ausinfo),von/bis(ISO-Datum, Pflicht) - Response
200:{ "status": "ok", "slots": ["2026-09-20T09:00:00Z", "…"] } - Fehler:
400fehlende Parameter ·500bei internem Fehler
cURL
curl "https://app.pronto.example/api/public/kalender/slots?kalenderId=KAL_ID&von=2026-09-20&bis=2026-09-27"
JavaScript
const res = await fetch(
`https://app.pronto.example/api/public/kalender/slots?kalenderId=KAL_ID&von=2026-09-20&bis=2026-09-27`,
);
const { slots } = await res.json();
PHP
<?php
$url = "https://app.pronto.example/api/public/kalender/slots?" . http_build_query([
"kalenderId" => "KAL_ID", "von" => "2026-09-20", "bis" => "2026-09-27",
]);
$slots = json_decode(file_get_contents($url), true)["slots"];
Python
r = requests.get(
"https://app.pronto.example/api/public/kalender/slots",
params={"kalenderId": "KAL_ID", "von": "2026-09-20", "bis": "2026-09-27"},
)
slots = r.json()["slots"]
POST /api/public/kalender/buchen
Bucht einen Termin.
- Methode:
POST - Authentication: keine
- Request-Body (JSON):
Pflichtfelder:{ "kundeId": "WORKSPACE_ID", "kalenderId": "KAL_ID", "startISO": "2026-09-20T09:00:00Z", "vorname": "Max", "nachname": "Mustermann", "email": "max@example.com", "telefon": "+49 151 00000000", "nachricht": "Optional", "besucherZeitzone": "Europe/Berlin", "ortTyp": "google_meet" }kundeId,kalenderId,startISO,email,vorname. - Response
200:{ "status": "ok", … }(Termin- und ggf. Meeting-Link-Details) - Fehler:
400Pflichtfelder fehlen/ungültige Anfrage ·409 { "status": "belegt" }Slot bereits vergeben
cURL
curl -X POST "https://app.pronto.example/api/public/kalender/buchen" \
-H "Content-Type: application/json" \
-d '{"kundeId":"WORKSPACE_ID","kalenderId":"KAL_ID","startISO":"2026-09-20T09:00:00Z","vorname":"Max","email":"max@example.com"}'
JavaScript
const res = await fetch("https://app.pronto.example/api/public/kalender/buchen", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
kundeId: "WORKSPACE_ID",
kalenderId: "KAL_ID",
startISO: "2026-09-20T09:00:00Z",
vorname: "Max",
email: "max@example.com",
}),
});
const ergebnis = await res.json();
PHP
<?php
$payload = json_encode([
"kundeId" => "WORKSPACE_ID", "kalenderId" => "KAL_ID",
"startISO" => "2026-09-20T09:00:00Z", "vorname" => "Max", "email" => "max@example.com",
]);
$ch = curl_init("https://app.pronto.example/api/public/kalender/buchen");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
]);
$ergebnis = json_decode(curl_exec($ch), true);
Python
r = requests.post(
"https://app.pronto.example/api/public/kalender/buchen",
json={
"kundeId": "WORKSPACE_ID",
"kalenderId": "KAL_ID",
"startISO": "2026-09-20T09:00:00Z",
"vorname": "Max",
"email": "max@example.com",
},
)
ergebnis = r.json()
GET /api/public/kalender/embed.js
Liefert das einbettbare Buchungsseiten-Skript (siehe
09-buchungsseiten.md für die Einbindung per <script>-Tag).
Keine eigene Aufruf-Logik nötig — das Skript ruft info/slots/buchen selbst auf.
Formulare
GET /api/public/formulare/info
Lädt die Struktur (Seiten, Felder, Design) eines veröffentlichten Formulars.
- Methode:
GET· Authentication: keine - Query-Parameter:
workspace(Pflicht),form(Formular-ID, Pflicht) - Response
200:{ "status": "ok", "formular": { "id": "…", "name": "…", "seiten": […], "design": {…} } } - Fehler:
400fehlende Parameter ·404 { "status": "nicht_gefunden" }
cURL
curl "https://app.pronto.example/api/public/formulare/info?workspace=WORKSPACE_ID&form=FORM_ID"
JavaScript
const { formular } = await fetch(
`https://app.pronto.example/api/public/formulare/info?workspace=WORKSPACE_ID&form=FORM_ID`,
).then((r) => r.json());
PHP
<?php
$url = "https://app.pronto.example/api/public/formulare/info?workspace=WORKSPACE_ID&form=FORM_ID";
$formular = json_decode(file_get_contents($url), true)["formular"];
Python
formular = requests.get(
"https://app.pronto.example/api/public/formulare/info",
params={"workspace": "WORKSPACE_ID", "form": "FORM_ID"},
).json()["formular"]
POST /api/public/formulare/submit
Sendet eine Formular-Einsendung. Der Body ist multipart/form-data (nicht JSON), damit
Datei-Uploads möglich sind.
- Methode:
POST· Authentication: keine - Form-Felder:
workspace(Pflicht),formularId(Pflicht)werte— JSON-String der Feldwerte, z. B.{"feld_email":"max@example.com"}datenschutzAkzeptiert—"true"/"false"marketingOptin—"true"/"false"website— Honeypot-Feld, immer leer lassen (siehe 24-rate-limits.md)ladezeitMs— Millisekunden seit Formularanzeige (Bot-Schutz)datei:<feldId>— optionale Datei-Uploads, ein Eintrag pro Datei-Feld
- Response
200:{ "status": "ok", "erfolg": { "typ": "nachricht", "titel": "Danke!", … } } - Fehler:
400fehlende Pflichtfelder/ungültige Anfrage ·429echtes Rate-Limit erreicht (Spam wird bewusst mit200beantwortet, siehe 24-rate-limits.md)
cURL
curl -X POST "https://app.pronto.example/api/public/formulare/submit" \
-F "workspace=WORKSPACE_ID" \
-F "formularId=FORM_ID" \
-F 'werte={"feld_email":"max@example.com","feld_name":"Max Mustermann"}' \
-F "datenschutzAkzeptiert=true" \
-F "website=" \
-F "ladezeitMs=4200"
JavaScript
const form = new FormData();
form.append("workspace", "WORKSPACE_ID");
form.append("formularId", "FORM_ID");
form.append("werte", JSON.stringify({ feld_email: "max@example.com", feld_name: "Max Mustermann" }));
form.append("datenschutzAkzeptiert", "true");
form.append("website", ""); // Honeypot — leer lassen
form.append("ladezeitMs", String(Date.now() - formularAngezeigtAb));
const res = await fetch("https://app.pronto.example/api/public/formulare/submit", {
method: "POST",
body: form,
});
const ergebnis = await res.json();
PHP
<?php
$ch = curl_init("https://app.pronto.example/api/public/formulare/submit");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => [
"workspace" => "WORKSPACE_ID",
"formularId" => "FORM_ID",
"werte" => json_encode(["feld_email" => "max@example.com", "feld_name" => "Max Mustermann"]),
"datenschutzAkzeptiert" => "true",
"website" => "",
"ladezeitMs" => "4200",
],
]);
$ergebnis = json_decode(curl_exec($ch), true);
Python
r = requests.post(
"https://app.pronto.example/api/public/formulare/submit",
data={
"workspace": "WORKSPACE_ID",
"formularId": "FORM_ID",
"werte": '{"feld_email":"max@example.com","feld_name":"Max Mustermann"}',
"datenschutzAkzeptiert": "true",
"website": "",
"ladezeitMs": "4200",
},
)
ergebnis = r.json()
GET /api/public/formulare/embed.js
Liefert das einbettbare Formular-Skript (siehe 10-formulare.md).
Newsletter
POST /api/public/newsletter/signup
- Methode:
POST· Authentication: keine - Request-Body (JSON):
{ "kundeId": "WORKSPACE_ID", "email": "max@example.com", "vorname": "Max", "quelle": "landingpage", "website": "", "ladezeitMs": 4200, "datenschutzAkzeptiert": true }datenschutzAkzeptiert: trueist serverseitig zwingend erforderlich. - Response
200:{ "status": "ok" }oder{ "status": "bestaetigung_erforderlich" }(Double-Opt-in-Mail wurde verschickt) - Fehler:
400fehlende Einwilligung/ungültige Anfrage ·429Rate-Limit
cURL
curl -X POST "https://app.pronto.example/api/public/newsletter/signup" \
-H "Content-Type: application/json" \
-d '{"kundeId":"WORKSPACE_ID","email":"max@example.com","vorname":"Max","datenschutzAkzeptiert":true}'
JavaScript
const res = await fetch("https://app.pronto.example/api/public/newsletter/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
kundeId: "WORKSPACE_ID",
email: "max@example.com",
vorname: "Max",
datenschutzAkzeptiert: true,
}),
});
const ergebnis = await res.json();
PHP
<?php
$payload = json_encode([
"kundeId" => "WORKSPACE_ID", "email" => "max@example.com",
"vorname" => "Max", "datenschutzAkzeptiert" => true,
]);
$ch = curl_init("https://app.pronto.example/api/public/newsletter/signup");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
]);
$ergebnis = json_decode(curl_exec($ch), true);
Python
r = requests.post(
"https://app.pronto.example/api/public/newsletter/signup",
json={
"kundeId": "WORKSPACE_ID",
"email": "max@example.com",
"vorname": "Max",
"datenschutzAkzeptiert": True,
},
)
ergebnis = r.json()
GET /api/public/newsletter/confirm
Double-Opt-in-Bestätigungslink — wird ausschließlich aus der Anmelde-E-Mail heraus
aufgerufen (?token=…), liefert eine fertige HTML-Bestätigungsseite. Nicht für eigene
Integrationen gedacht.
GET /api/public/newsletter/unsubscribe
Abmeldelink, automatisch in jeder Newsletter-Mail (?rid=…), liefert eine fertige
HTML-Seite. Ebenfalls nicht für eigene Integrationen gedacht.
Chat-Widget
GET /api/public/widget.js
Liefert das einbettbare Chat-Widget-Skript. Einbindung:
<script src="https://app.pronto.example/api/public/widget.js" data-pronto="WIDGET_KEY"></script>
GET /api/public/widget/config
Lädt die öffentliche Anzeigekonfiguration (Farbe, Modus, Aktiv-Status) für einen
Widget-Key. CORS-fähig (OPTIONS-Preflight unterstützt).
- Query-Parameter:
key(Pflicht) - Response
200:{ "aktiv": true, … }— bei inaktivem Widget, falscher Domain oder aufgebrauchtem Credit-Guthaben immer{ "aktiv": false }(kein Fehlercode, damit das Widget auf der Kundenseite einfach unsichtbar bleibt).
POST /api/public/widget/message
Sendet eine Chat-Nachricht.
- Request-Body (JSON):
{ "key": "WIDGET_KEY", "session_id": "…", "text": "…", "typ": "text" }(typ:"text"|"button"|"kontakt") - Fehler:
400ungültige Anfrage ·403Domain nicht freigegeben ·404Widget inaktiv/nicht gefunden
POST /api/public/widget/termin
Rückmeldung nach einer im Widget gebuchten Terminanfrage (event_uri, name, email,
telefon).
Diese drei Widget-Endpunkte werden ausschließlich vom mitgelieferten widget.js-Skript
selbst aufgerufen — eine eigene Implementierung dagegen ist möglich, aber nicht der
vorgesehene Weg (das Skript einbinden reicht).
Nicht Teil dieser Referenz
api/public/pronto-ai/chat (Bearer-Token-authentifiziert, nur für eingeloggte
PRONTO-Nutzer, aktuell deaktiviert), alle api/public/*/webhook und */run-Endpunkte
(interne Callbacks, siehe 19-webhooks.md) sowie oauth/*/callback
(OAuth-Redirect-Ziele) sind keine für externe Integrationen vorgesehenen Endpunkte.
