Eigene API bauen — Tools per API-Key aufrufen
Tools als authentifizierte REST-API freigeben, API-Keys verwalten, OpenAPI-Spec abrufen. Für Server-zu-Server-Anbindungen an Datenbanken, Sheets und eigene Logik.
Eigene API bauen
Mit API-Keys wird dein Workspace zu einer REST-API für deine Bestandssysteme. Du schreibst ein Tool (oder lässt den Builder eins schreiben), gibst es für die API frei und legst im Backend einen Key an. Andere Systeme — Shop, ERP, Zapier, n8n, ein Partner — rufen das Tool dann per HTTP auf.
Typische Fälle:
- eine Bestandsdatenbank (Shop, CMS, Eigenbau) lesend als API bereitstellen → Rezept
- ein Google Sheet als kleine Datenbank mit API nutzen → Rezept
- eigene Fachlogik (Preisberechnung, Verfügbarkeit, Lead-Anlage) für andere Systeme öffnen
Die Plattform kümmert sich um Authentifizierung, Freigabe, Rate-Limit, Parameter-Prüfung, Logging und die OpenAPI-Spec. Die Fachlogik bleibt in deinem Tool.
Abgrenzung zur Public Tool API: Die Public Tool API ist für anonyme Aufrufe aus dem Browser (z. B. Landingpage mit Token-Link). Die API-Key-API ist für Server-zu-Server mit Geheimnis. Beide Freigaben sind unabhängig voneinander.
Nicht pollen — Webhooks: Soll dein System erfahren, wann etwas passiert (neue Inbox-Nachricht, Tool fertig, Scheduled Task fehlgeschlagen), statt regelmäßig
/api/v1abzufragen, richte einen ausgehenden Webhook ein. Umgekehrt kann ein fremdes System über einen eingehenden Webhook ein Tool auslösen, ohne einen API-Key zu verwalten — Tools mitexternal_apisind dafür automatisch wählbar. Beispiel mit n8n: Rezept.
In drei Schritten
1. Tool freigeben — in der Tool-Definition:
'getKunde' => [
'description' => 'Liefert Stammdaten eines Kunden per Kundennummer.',
'internal' => true,
'external_api' => true, // für API-Keys freigeben
'parameters' => [
'type' => 'object',
'properties' => [
'kundennummer' => ['type' => 'string', 'pattern' => '^K[0-9]{5}$'],
],
'required' => ['kundennummer'],
],
'handler' => function (array $results, array $args, array $ctx): array {
// …
},
],
2. Key anlegen — Backend → Verwaltung → API-Keys → „Neuer API-Key". Name vergeben, Tools anhaken, Limit setzen. Der Key (tm_live_…) wird genau einmal angezeigt. TaskMonkey speichert nur einen Hash; ein verlorener Key lässt sich nicht wiederherstellen, nur ersetzen.
3. Aufrufen:
curl -X POST https://app.taskmonkey.de/api/v1/tools/getKunde \
-H 'Authorization: Bearer tm_live_…' \
-H 'Content-Type: application/json' \
-d '{"args":{"kundennummer":"K10001"}}'
{"success":true,"found":true,"kunde":{"kundennummer":"K10001","firma":"Gärtnerei Blum","ort":"Bremen","umsatz_vorjahr":48210.5}}
Freigabe: zwei Schlüssel, beide nötig
Ein Aufruf geht nur durch, wenn beides zutrifft:
- Das Tool trägt
'external_api' => true(genautrue, nicht1oder"yes"). - Der Key hat das Tool in seiner Liste.
Fehlt eins, antwortet die API mit 403 „Tool nicht verfuegbar" — dieselbe Antwort, egal ob das Tool nicht existiert, nicht freigegeben ist oder nicht im Key steht. So lässt sich die Tool-Liste nicht von außen abtasten.
Weitere Regeln:
- Der Workspace kommt aus dem Key, nicht aus der URL. Ein Key kann nie Tools eines anderen Workspaces erreichen.
- Tools mit
async: truesind über die API nicht aufrufbar. public_apiöffnet ein Tool nicht für Keys und umgekehrt.- Der Builder setzt
external_api,external_api_writeundpublic_apinur nach einer eigenen Rückfrage im Chat. Technisch lehntwriteTenantConfigjede Änderung ab, die eines dieser Flags neu auftruesetzt, solange der Aufruf nichtconfirm_exposure=trueträgt.
Felder in der Tool-Definition
| Feld | Pflicht | Bedeutung |
|---|---|---|
external_api |
ja | true gibt das Tool für API-Keys frei |
external_api_write |
nein | true erlaubt Schreibzugriffe (siehe Nur lesend) |
external_api_description |
nein | Beschreibung für Entwickler in der OpenAPI-Spec (statt der LLM-orientierten description) |
external_api_response |
nein | JSON Schema der Antwort, landet in der OpenAPI-Spec |
parameters |
empfohlen | JSON Schema der Args — wird vor dem Aufruf geprüft |
Parameter werden geprüft und gefiltert
Bevor das Tool läuft, gleicht die API die args mit dem parameters-Schema ab:
- Nicht deklarierte Parameter werden verworfen. Viele Handler lassen sich über Args umkonfigurieren (z. B.
slugbei Sheets-Tools). Über die API geht das nur, wenn das Tool den Parameter ausdrücklich deklariert. - Geprüft werden
required,type,enum,minLength/maxLength,pattern,minimum/maximum,maxItemsunditems.type. Verstöße → 400 mit Liste indetails. - Das Tool bekommt zusätzlich
args['_api_key']mitid,nameund gekürzter IP des Aufrufers. Dieser Wert wird serverseitig gesetzt und lässt sich nicht fälschen.
Je enger das Schema, desto sicherer das Tool. Ein pattern für IDs kostet eine Zeile und hält Unfug fern, bevor die Datenbank angefasst wird.
Standardmäßig nur lesend
Tools laufen über die API im Read-only-Kontext ($ctx['read_only'] === true), auch alle Tools, die sie intern aufrufen. Dann gilt:
executePreparedQuerymitmode: 'write'wird verweigertexecuteQuery(freies SQL) ist über die API immer gesperrtwriteToGoogleSheetundclearFromGoogleSheetwerden verweigert
Ein Tool, das schreiben soll, deklariert das ausdrücklich:
'external_api' => true,
'external_api_write' => true, // bewusst schreibend — Begründung im Kommentar
Der Read-only-Kontext deckt DB-Gateway und Google Sheets ab. Andere Seiteneffekte (Mails, deklarative
api-Calls mit POST) hält er nicht auf — dafür ist die Freigabe selbst da: Gib nur Tools frei, deren Wirkung du für Key-Inhaber verantworten kannst.
Statuscodes
| Status | Bedeutung | Body |
|---|---|---|
| 200 | Tool wurde ausgeführt | Rückgabe des Tools (kann success: false enthalten) |
| 400 | Parameter ungültig oder Tool mit Exception abgebrochen | {"success":false,"error":"Ungueltige Parameter","details":[…]} bzw. neutrale Meldung |
| 401 | Key fehlt, falsches Format, unbekannt, widerrufen oder abgelaufen | {"success":false,"error":"Ungueltiger oder fehlender API-Key"} + WWW-Authenticate: Bearer |
| 403 | Tool nicht verfügbar (siehe Freigabe) oder Workspace gesperrt | {"success":false,"error":"Tool nicht verfuegbar"} |
| 405 | Falsche Methode (Tools: nur POST, Spec: nur GET) | |
| 429 | Rate-Limit erreicht | {"success":false,"error":"…","retry_after":17} + Retry-After |
| 500 | Unerwarteter Fehler | {"success":false,"error":"Interner Fehler"} |
Interne Fehlermeldungen gehen nie nach außen, sie stehen im Log.
Rate-Limit
Jeder Key hat ein Limit in Aufrufen pro Minute (Default 60, einstellbar 1–10 000). Gezählt wird in einem festen Minutenfenster (:00 bis :59), nicht gleitend. Jede Antwort mit gültigem Key trägt:
| Header | Inhalt |
|---|---|
X-RateLimit-Limit |
Limit pro Minute |
X-RateLimit-Remaining |
verbleibende Aufrufe im aktuellen Fenster |
X-RateLimit-Reset |
Unix-Zeit, zu der das Fenster endet |
Retry-After |
nur bei 429: Sekunden bis zum nächsten Fenster |
Abgelehnte Aufrufe (429) zählen nicht mit. Bei vielen gleichzeitigen Requests kann das Limit kurzzeitig um deren Anzahl überschritten werden.
API-Aufrufe werden derzeit nicht auf das Kontingent deines Plans angerechnet.
OpenAPI-Spec
curl https://app.taskmonkey.de/api/v1/openapi.json -H 'Authorization: Bearer tm_live_…'
liefert eine OpenAPI 3.1-Spec mit genau den Tools, die dieser Key aufrufen darf — erzeugt aus den parameters-Schemas. Damit lassen sich Clients generieren, Postman/Insomnia befüllen oder die API in Tools wie n8n, Make oder einen GPT/Agenten einbinden. Ohne Key gibt es 401: die Tool-Liste ist nicht öffentlich.
Keys verwalten
Backend → Verwaltung → API-Keys (/manage/api-keys, Rolle Manager oder Admin):
- Liste mit Status, Präfix, erlaubten Tools, Limit, zuletzt benutzt, Aufrufe der letzten 24 h und gesamt
- Bearbeiten: Name, Tools, Limit, Ablaufdatum; darunter die letzten 50 Aufrufe mit Status und Dauer
- Widerrufen wirkt sofort (nächster Aufruf → 401). Der Eintrag bleibt fürs Audit sichtbar.
Tools, die im Key stehen, aber nicht (mehr) external_api tragen, werden rot markiert — Aufrufe darauf enden mit 403.
Im Builder
Im Builder (/manage/builder → Tools → Tool öffnen) hat jedes Tool einen Tab API. Für ein Tool mit external_api zeigt er:
- Endpoint (
POST {Base-URL}/api/v1/tools/{tool}) und den HeaderAuthorization: Bearer tm_live_…— nur als Platzhalter, ein echter Key wird nie angezeigt - Beispiel-Request aus
args_fixturebzw. demparameters-Schema (nur deklarierte Felder — alles andere verwirft die API ohnehin) - Snippets für curl, JavaScript, Python, PHP (Guzzle), PHP (curl) und eine Postman-Collection v2.1 (Key als Collection-Variable
apiKey). Die Code-Snippets lesen den Key aus der UmgebungsvariableTASKMONKEY_API_KEY, damit er nicht im Code landet. - welche Keys das Tool erlauben (Name, Präfix, Limit, Status) mit Link auf API-Keys — ohne passenden Key gibt es einen Hinweis
- Link und curl-Aufruf für die OpenAPI-Spec, Hinweis nur lesend bzw. schreibend (
external_api_write) - Statuscodes, Rate-Limit-Header, Sicherheits-Checkliste, Beispiel-Response (echte letzte Antwort mit maskierten Feldern oder schematisch)
Ist das Tool (noch) nicht freigegeben, erklärt der Tab den Unterschied zur Public Tool API und bietet an, die Freigabe per Builder-Chat vorzunehmen (der zeigt die Änderung vor dem Schreiben). Ist external_api gesetzt, aber nicht exakt true — oder das Tool ist async —, warnt der Tab, dass die Freigabe nicht greift.
Ausprobieren funktioniert nur mit einem Key, den du dort einfügst. Er wird nicht gespeichert, nur mit diesem einen Request gesendet und beim Verlassen des Tabs verworfen. Der Aufruf läuft echt (inklusive Nutzungs-Log und Rate-Limit des Keys).
Sicherheit
- Keys sind Passwörter und gehören ins Backend. Nie in Browser-JavaScript, Apps, öffentliche Repos oder Screenshots. Die API erzwingt das zum Teil selbst:
/api/v1/*sendet keine CORS-Header (auch nicht beim Preflight), Browser verweigern Aufrufe von fremden Domains also. Wenn deine Website die Daten braucht, ruft dein eigener Server die API auf und reicht nur das Nötige an den Browser weiter. Für anonyme Aufrufe direkt aus dem Browser ist die Public Tool API gedacht (dort ist CORS offen). - Ein Key pro System. Dann lässt sich ein einzelnes System widerrufen, ohne die anderen zu stören, und die Nutzung ist pro System sichtbar.
- Ablaufdatum setzen, wenn ein Key nur für ein Projekt gebraucht wird.
- Nur Anzeige-Felder zurückgeben. Was das Tool zurückgibt, sieht der Aufrufer. Keine internen IDs, Tokens oder Metadaten durchreichen.
- Schreibende Tools begründen. Neben
external_api_writesteht im Kommentar, warum das vertretbar ist.
Checkliste für jedes Tool mit external_api:
-
parametersso eng wie möglich (pattern,enum,maximum) - SQL steht fest im Tool, Werte nur über
params(executePreparedQuery) - Rückgabe auf die nötigen Felder reduziert
-
external_api_writenur, wo wirklich geschrieben wird - Keine Mails, LLM-Calls oder teuren Seiteneffekte, die ein Key-Inhaber beliebig oft auslösen kann — oder bewusst mit niedrigem Limit
Logging
Jeder Aufruf mit gültigem Key landet im Nutzungs-Log (api_key_requests: Key, Tool, Status, Dauer, gekürzte IP) — sichtbar in der Key-Ansicht. Einträge werden nach 90 Tagen gelöscht (täglicher Cron prune_api_key_requests); die Gesamtzahl und „zuletzt benutzt" am Key bleiben erhalten. Der Tool-Lauf selbst erscheint zusätzlich wie gewohnt im Execution-Log mit Entry-Type api_key und einer Chat-ID apikey_<key-id>_<zeit>_<zufall> (tm monitor, tm watch).
Testen
Tool-Logik lokal wie jedes andere Tool:
bin/cake test_tool -t meinworkspace --tool getKunde --args='{"kundennummer":"K10001"}'
test_tool läuft ohne Read-only-Kontext und ohne Parameter-Filter. Den kompletten Weg prüfst du per curl gegen /api/v1/… mit einem Test-Key.
Technik
| Was | Wo |
|---|---|
| Endpoint | src/Controller/Api/ExternalApiController.php |
| Key-Logik, Gate, Rate-Limit | src/Service/ApiKey/ApiKeyService.php |
| Parameter-Prüfung | src/Service/ApiKey/ToolArgsValidator.php |
| OpenAPI | src/Service/ApiKey/OpenApiBuilder.php |
| Verwaltung | src/Controller/Manage/ApiKeysController.php |
| Routen | config/routes.php (/api/v1/tools/{tool}, /api/v1/openapi.json) |
| Tabellen | api_keys, api_key_requests |
Siehe auch: Webhooks — Ereignisse senden und Tools per eingehendem Webhook auslösen.