Public Tool API — Tools von externen Seiten aufrufen
Ein Tool per HTTP ohne Login aufrufbar machen, z.B. für Landingpages oder Formulare auf fremden Domains. Freigabe, Aufruf, Sicherheit.
Public Tool API
Mit der Public Tool API ruft eine extern gehostete Seite ein Tool deines Workspaces direkt per HTTP auf — ohne Login, ohne Chat, ohne LLM. Typische Fälle:
- ein Formular auf der Kunden-Website, das Daten in deinen Workspace schreibt
- eine Landingpage, die personalisierte Inhalte aus einem Token-Link lädt
- ein Upload-Formular, das nach dem Hochladen eine Einwilligung protokolliert
Die Fachlogik bleibt komplett in deinem Tool. Die Plattform stellt nur den Ausführungsrahmen: Freigabe-Prüfung, Rate-Limit, CORS, Fehlerbehandlung.
Wichtig: Ein freigegebenes Tool ist für jeden aufrufbar, der die URL kennt. Die Autorisierung gehört deshalb ins Tool. Lies den Abschnitt Sicherheit, bevor du ein Tool freigibst.
Server-zu-Server mit Zugangsschlüssel? Dafür gibt es Eigene API bauen: Aufrufe mit API-Key (
/api/v1/tools/{tool}), Freigabe per'external_api' => true, Rate-Limit pro Key, standardmäßig nur lesend, OpenAPI-Spec. Die Public Tool API bleibt für anonyme Aufrufe aus dem Browser — beide Freigaben sind unabhängig.
Aufruf
POST https://app.taskmonkey.de/api/public/tool/{tenantCode}/{toolName}
Content-Type: application/json
{ "args": { ... } }
tenantCode— Code deines Workspaces (z.B.bloomify)toolName— Name des Tools, exakt wie in der Tool-Definitionargs— die Tool-Parameter, wie imparameters-Schema des Tools beschrieben
Die Antwort ist das JSON, das dein Tool zurückgibt — das Tool bestimmt also selbst, was der Aufrufer sieht, inklusive success.
Beispiel mit curl
curl -X POST https://app.taskmonkey.de/api/public/tool/bloomify/resolvePhotoInvite \
-H 'Content-Type: application/json' \
-d '{"args":{"campaign":"pflanzen-fotos-2026","token":"<32-hex-token>"}}'
Beispiel im Browser
function callTool(name, args) {
return fetch('https://app.taskmonkey.de/api/public/tool/bloomify/' + encodeURIComponent(name), {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({ args: args })
}).then(function (r) {
return r.json().then(function (j) {
if (!j.success) throw new Error(j.error || 'Die Anfrage ist fehlgeschlagen.');
return j;
});
});
}
CORS ist für alle Origins offen (Access-Control-Allow-Origin: *), der Aufruf funktioniert also von jeder Domain aus.
Statuscodes
| Status | Bedeutung | Body |
|---|---|---|
| 200 | Tool wurde ausgeführt | Rückgabe des Tools (kann success: false enthalten, z.B. bei ungültigem Token) |
| 400 | Tool ist mit einer Exception abgebrochen | {"success":false,"error":"Die Anfrage konnte nicht verarbeitet werden."} |
| 403 | Tool existiert nicht oder ist nicht freigegeben | {"success":false,"error":"Tool nicht verfuegbar"} |
| 404 | Unbekannter Workspace | {"success":false,"error":"Unbekannter Tenant"} |
| 405 | Andere Methode als POST | {"success":false,"error":"Nur POST erlaubt"} |
| 429 | Rate-Limit erreicht | {"success":false,"error":"Zu viele Anfragen. …"} |
| 500 | Unerwarteter Fehler | {"success":false,"error":"Interner Fehler"} |
403 unterscheidet bewusst nicht zwischen „gibt es nicht" und „nicht freigegeben" — so lässt sich die Tool-Liste eines Workspaces nicht von außen abfragen. Interne Fehlermeldungen gehen nie nach außen, sie stehen im Log.
Ein Tool freigeben
Die Freigabe steht in der Tool-Definition selbst. Es gibt keine zweite Liste an anderer Stelle — wer ein Tool öffnet, tut das dort, wo auch sein Code steht.
return [
'tools' => [
'resolvePhotoInvite' => [
'description' => 'Löst einen Einladungs-Token auf …',
'internal' => true,
// Öffentlich aufrufbar über /api/public/tool/bloomify/resolvePhotoInvite.
// Begründung im Kommentar festhalten: warum ist das vertretbar?
'public_api' => true,
'public_api_rate_limit' => 60, // optional, Default 60
'parameters' => [ /* … */ ],
'handler' => function (array $results, array $args, array $ctx): array {
// …
},
],
],
];
| Feld | Pflicht | Bedeutung |
|---|---|---|
public_api |
ja | true gibt das Tool frei. Fehlt es, antwortet der Endpoint mit 403 (fail-closed). |
public_api_rate_limit |
nein | Max. Aufrufe pro IP und Tool (Default 60). Der Zähler verfällt eine Stunde nach dem letzten Aufruf. |
Das Tool kann weiterhin internal: true sein — die Public-Freigabe ist unabhängig davon, ob der Chat es sieht.
Über den Builder-Chat wird public_api nur nach einer eigenen Rückfrage gesetzt: writeTenantConfig lehnt eine neue Freigabe ohne confirm_exposure=true ab.
Was das Tool zusätzlich bekommt
Der Endpoint hängt unter args['_client'] Kontext an, den ein Tool für Nachweise braucht (z.B. bei einer Einwilligung):
| Feld | Inhalt |
|---|---|
_client.ip_prefix |
IP gekürzt auf /24 (IPv4) bzw. /64 (IPv6) — für Nachweise ausreichend, keine volle Adresse |
_client.user_agent |
User-Agent des Browsers (max. 500 Zeichen) |
_client.origin |
Origin-Header der aufrufenden Seite (max. 200 Zeichen) |
Diese Werte setzt der Server nach den mitgeschickten Args — ein Aufrufer kann _client nicht fälschen.
Der Aufruf läuft ohne Session und ohne Benutzer. Im Execution-Log erscheint er mit Entry-Type public_api und einer Chat-ID public_<zeitstempel>_<zufall>.
Jeder Aufruf löst außerdem das Webhook-Event public_tool.submitted aus — z. B. um Formular-Eingänge in n8n oder ein CRM weiterzureichen. Die Formulardaten selbst gehen nur mit, wenn das Tool 'webhook_event' => true trägt. Siehe Webhooks.
Sicherheit
Weil jeder die URL aufrufen kann, muss ein freigegebenes Tool eine dieser beiden Bedingungen erfüllen:
- Es verlangt ein Geheimnis in den Args — z.B. einen nicht ratbaren Token aus einem Mail-Link (32 Hex-Zeichen oder mehr). Ohne gültigen Token gibt es nichts heraus.
- Es ist ohne jede Autorisierung unbedenklich — z.B. eine reine Verfügbarkeits-Abfrage ohne personenbezogene Daten.
Checkliste für jedes Tool mit public_api:
- Format zuerst prüfen. Token-Format per Regex validieren, bevor die Datenbank angefasst wird — so belasten Scanner und Müll-URLs nichts.
- Neutrale Fehler. Ein unbekannter Token bekommt dieselbe Antwort wie ein Token aus einer fremden Kampagne. Sonst lässt sich abfragen, welche Tokens existieren.
- Nur Anzeige-Felder zurückgeben. Keine E-Mail-Adressen, keine internen IDs, keine Metadaten. Tokens in URLs landen in Browser-History und Referrern — was rausgeht, muss auch dann harmlos bleiben.
- Keine teuren oder gefährlichen Seiteneffekte. Tools, die Mails verschicken, Listen ausgeben, LLM-Calls machen oder sonst Kosten verursachen, gehören nicht in die Public API.
- Rate-Limit passend setzen. Ein Token-Auflöser verträgt mehr Aufrufe als ein Upload-Bestätiger. Achtung bei Mobilfunk: viele Nutzer teilen sich eine IP (Carrier-NAT). Das Limit nicht so knapp wählen, dass eine Mailing-Welle echte Kunden aussperrt.
- Freigabe begründen. Im Kommentar neben
public_apisteht, warum die Freigabe vertretbar ist.
Im Builder
Jedes Tool hat im Builder (/manage/builder → Tools → Tool öffnen) einen Tab API. Er ist immer sichtbar und zeigt je nach Freigabe:
- Nicht freigegeben: was
public_apiundexternal_apibedeuten, wann welches passt, die wichtigsten Sicherheitsregeln und einen Button „Öffentlich freigeben lassen". Der Button schickt einen vorformulierten Auftrag an den Builder-Chat — der prüft die Checkliste, zeigt dir die Änderung und schreibt sie erst nach deinem OK. Der Tab selbst ändert nichts an der Tool-Definition. - Mit
public_api: Endpoint-URL mit echtem Workspace-Code, Parameter-Tabelle, Beispiel-Request und fertige Snippets für curl, JavaScript (fetch), Python (requests), PHP (Guzzle), PHP (curl) und eine Postman-Collection (v2.1) zum Kopieren oder Herunterladen. Dazu Statuscodes, das Rate-Limit des Tools und die Checkliste von oben.
Der Beispiel-Request nimmt die args_fixture des Tools, wenn es eine gibt; sonst werden Beispielwerte aus dem parameters-Schema erzeugt (default, example, enum, format, einfache pattern). Werte in <spitzen Klammern> sind Platzhalter. Alle Snippets entstehen aus derselben Request-Beschreibung (src/Service/ApiKey/ApiSnippetGenerator.php), sie können also nicht auseinanderlaufen.
Die Beispiel-Response ist, wenn möglich, die echte Antwort der letzten erfolgreichen Chat-Ausführung des Tools — gekürzt (Listen auf 3 Einträge, lange Texte abgeschnitten) und mit maskierten sensiblen Feldern (E-Mail, Telefon, Token, Key, Passwort, IBAN … nach Feldname und Wert). Gibt es keine, zeigt der Tab ein schematisches Beispiel.
Ausprobieren: Ein Formular aus dem Schema ruft den echten Endpoint auf und zeigt Status, Dauer und Antwort. Das Tool läuft dabei wirklich — mit allen Seiteneffekten, und der Aufruf zählt auf das Rate-Limit deiner IP. Deshalb muss man das vor dem Senden per Häkchen bestätigen.
Testen
Lokal testest du die Tool-Logik wie jedes andere Tool (siehe Tools testen):
bin/cake test_tool -t bloomify --tool resolvePhotoInvite --args='{"campaign":"x","token":"abc"}'
Den HTTP-Weg inkl. Freigabe prüfst du per curl gegen den lokalen Server oder Prod:
# Nicht freigegebenes Tool → 403
curl -s -X POST -H 'Content-Type: application/json' -d '{"args":{}}' \
https://app.taskmonkey.de/api/public/tool/bloomify/listCalendarEvents
# Freigegebenes Tool mit ungültigem Token → 200 mit success:false
curl -s -X POST -H 'Content-Type: application/json' -d '{"args":{"campaign":"x","token":"abc"}}' \
https://app.taskmonkey.de/api/public/tool/bloomify/resolvePhotoInvite
Referenz-Implementierung: Bloomify Foto-Mailing
Das erste Feature auf Basis der Public Tool API. Kunden bekommen eine Mail mit Token-Link, landen auf einer Upload-Seite auf einer fremden Domain, laden Fotos hoch und bestätigen die Einwilligung.
| Baustein | Datei |
|---|---|
| Token auflösen (Anzeige-Kontext) | config/tenants/bloomify/tools/mailing/resolvePhotoInvite.php |
| Upload + Einwilligung bestätigen | config/tenants/bloomify/tools/mailing/confirmPhotoUpload.php |
| Standalone-Upload-Seite (Vorlage) | docs/examples/foto-upload-standalone.html |
| Prozess-Doku | docs/FotoMailing.md |
Der Datei-Upload selbst läuft nicht über die Public Tool API, sondern über den Drive-Upload-Endpoint (/api/drive-upload/init/…), der vorher dasselbe Resolver-Tool zur Token-Prüfung aufruft.
Technik
| Was | Wo |
|---|---|
| Endpoint | src/Controller/Api/PublicToolController.php |
| Route | config/routes.php (/api/public/tool/{tenantCode}/{toolName}) |
| Auth-Ausnahme | src/Middleware/JwtAuthMiddleware.php |
| CORS | src/Middleware/CorsPreflightMiddleware.php |