Invoke-API
POST /api/invoke/{id} ist der native Weg, einen Agenten in BasePeak.AI
auszuführen. Anders als die OpenAI-kompatible Schnittstelle
legt sie den vollständigen Ereignisstrom des Agenten offen:
Werkzeugaufrufe, Schrittwechsel, OAuth-Rückfragen und Fehler – nicht nur den
Text der Antwort.
Verwenden Sie sie, wenn Sie anzeigen möchten, was der Agent gerade tut, oder wenn ein Zug lange läuft und keine Verbindung offen halten soll.
Anfrage
POST /api/invoke/{id} HTTP/1.1
Authorization: Bearer sk-bpai-1-42-…
Accept: text/event-stream
Content-Type: text/plain
Fasse den Q3-Bericht zusammen und nenne die drei größten Risiken.
Der Anfragekörper ist reiner Text, kein JSON
Das ist der häufigste Fehler beim Wechsel von der OpenAI-Schnittstelle. Der
Anfragekörper wird wörtlich als Eingabe für den Agenten verwendet. Es gibt
kein messages-Array, keinen JSON-Umschlag und kein Feld model.
Senden Sie hier {"messages":[…]}, erhält der Agent dieses JSON als
buchstäblichen Text Ihrer Frage.
Pfadparameter {id}
{id} bestimmt, was ausgeführt wird, und akzeptiert drei Formen:
| Form | Beispiel | Verhalten |
|---|---|---|
| Agenten-ID | a17jlm7 | Führt diesen Agenten aus. |
| Agenten-Alias | support-bot | Wird zum Agenten hinter dem Alias aufgelöst. |
| Thread-ID | t1abcde | Führt den Agenten des Threads aus und setzt diesen Thread fort. |
Der benötigte Scope lautet agent:chat:<Wert im Pfad>. Das unterscheidet
sich von /v1/chat/completions, wo er aus dem Header X-BPAI-Agent
abgeleitet wird.
Ein auf agent:chat:a17jlm7 beschränkter Schlüssel darf also
/api/invoke/a17jlm7 aufrufen. Ein Aufruf von /api/invoke/t1abcde mit einer
Thread-ID im Pfad benötigt hingegen agent:chat:t1abcde oder einen
Platzhalter-Schlüssel agent:chat:*. Um einen Thread mit einem
agentenbezogenen Schlüssel fortzusetzen, lassen Sie den Agenten im Pfad und
übergeben den Thread separat – siehe
Einen Thread auswählen.
Einen Thread auswählen
Zwei Routen tragen den Thread im Pfad, außerdem gibt es einen Header:
POST /api/invoke/{id}
POST /api/invoke/{id}/thread/{thread}
POST /api/invoke/{id}/threads/{thread}
| Weg | Vorrang |
|---|---|
Pfadsegment {thread} | Gewinnt, wenn vorhanden. |
Header X-BPAI-Thread-Id | Wird verwendet, wenn der Pfad keinen Thread trägt. |
| Keines von beidem | Ein neuer Thread wird angelegt. |
Die Antwort trägt immer X-BPAI-Thread-Id, bei neuen wie bei
fortgesetzten Threads. Halten Sie den Wert fest – das bleibt der direkteste
Weg, die Unterhaltung fortzusetzen. Siehe Threads.
Header
| Header | Erforderlich | Zweck |
|---|---|---|
Authorization | ja | Bearer sk-bpai-<user>-<key>-<secret> |
Accept | nein | Wählt das Antwortformat – siehe unten. |
X-BPAI-Thread-Id | nein | Einen Thread fortsetzen. |
X-BPAI-Scoped-Tools | nein | Den Lauf auf einen Teil der Werkzeuge beschränken. |
Query-Parameter
| Parameter | Standard | Bedeutung |
|---|---|---|
async | false | true liefert sofort IDs zurück statt zu streamen. |
Das Antwortformat wählen
Bei einem synchronen Aufruf entscheidet ausschließlich der Header Accept
darüber, was zurückkommt:
Accept | Antwort |
|---|---|
text/event-stream | SSE-Strom der Ereignisse, live während des Laufs. |
application/json | Ein JSON-Umschlag {"items":[…]} nach Ende des Laufs. |
| alles andere oder nicht gesetzt | Reiner Text: das content jedes Ereignisses, aneinandergefügt und gestreamt. |
Accept wird exakt verglichen – */* bricht das StreamingDer Headerwert wird buchstäblich mit dem gesamten Accept-Header
verglichen. Accept: text/event-stream funktioniert.
Accept: text/event-stream, */* nicht – es fällt in den Zweig für reinen
Text.
Viele HTTP-Clients hängen */* an oder senden es standardmäßig. Wenn Sie SSE
erwarten und einen Textblock ohne Rahmen erhalten, ist das die Ursache.
Senden Sie den Wert allein:
curl -H 'Accept: text/event-stream' … # richtig
curl -H 'Accept: text/event-stream, */*' … # stillschweigend KEIN SSE
Dasselbe gilt für application/json.
Die Variante mit reinem Text schreibt höchstens alle 500 ms aus; sie streamt also, aber in kleinen Blöcken statt Token für Token.
Server-Sent Events
Mit Accept: text/event-stream ist der Strom wie folgt gerahmt.
1. Der Strom öffnet sofort, bevor der Agent etwas erzeugt:
event: start
data: {}
2. Jedes Ereignis trägt die Lauf-ID als SSE-Feld id, danach folgt das
Ereignisobjekt als data:
id: r1xyz
data: {"runID":"r1xyz","threadID":"t1abcde","content":"Der Q3-Bericht "}
3. Das letzte Ereignis eines Laufs verwendet den eigenen id-Zusatz
:after zusammen mit runComplete:
id: r1xyz:after
data: {"runID":"r1xyz","runComplete":true,"content":""}
4. Keepalives erscheinen als SSE-Kommentare, sobald der Lauf 20 Sekunden still ist:
: ping
Das ist nötig, weil ein langsames erstes Token oder ein langer Werkzeugaufruf
den Socket sonst untätig ließe, bis ein zwischengeschalteter Proxy ihn
beendet. Kommentarzeilen verwirft
EventSource – wie jeder konforme SSE-Parser – automatisch; behandeln müssen
Sie sie nur, wenn Sie den Strom selbst parsen.
5. Der Strom schließt:
event: close
data: {}
Ein fehlendes event: close ist als unerwartetes Ende zu behandeln.
Das Ereignisobjekt
Jeder data:-Rahmen ist ein JSON-Objekt. Die Felder, die Sie tatsächlich
brauchen:
| Feld | Bedeutung |
|---|---|
content | Ausgabetext. Über alle Ereignisse aneinanderfügen, um die Antwort zu erhalten. |
contentID | Kennzeichnet einen Inhaltsstrom, sodass Fragmente zugeordnet werden können. |
runID / threadID | Zu welchem Lauf und Thread das Ereignis gehört. |
runComplete | true beim letzten Ereignis des Laufs. |
error | Gesetzt, wenn der Lauf gescheitert ist. |
time | Zeitpunkt der Entstehung. |
Weitere Felder, von denen je Ereignis höchstens eines gesetzt ist:
| Feld | Bedeutung |
|---|---|
waitingOnModel | Das Modell hat noch nicht begonnen zu antworten – gut für einen Ladeindikator. |
toolInput | Das Modell stellt Werkzeugargumente zusammen (kann dauern). |
toolCall | Ein Werkzeug wird aufgerufen. |
step | Der aktuelle Schritt hat gewechselt; folgende Ereignisse gehören dazu. |
prompt | Der Agent benötigt eine Eingabe – meist eine OAuth-Anmeldung. Enthält fields, message und ein Kennzeichen sensitive. |
input | Eingabe, die dem Lauf übergeben wurde. |
replayComplete | Alle bereits vorhandenen Ereignisse sind ausgeliefert; ab hier folgt Neues. |
username | Wer den Lauf ausgelöst hat. |
Die einfache Anbindung ist wirklich einfach: Ist keines der weiteren Felder
gesetzt, geben Sie einfach content aus. Die übrigen behandeln Sie nur,
wenn Sie den Fortschritt anzeigen möchten.
Feuern und vergessen mit async
curl -sS -X POST \
'https://your-instance.platform.basepeak.ai/api/invoke/a17jlm7?async=true' \
-H "Authorization: Bearer sk-bpai-1-42-…" \
--data 'Erstelle den Monatsbericht und maile ihn an das Team.'
Antwortet sofort:
{
"threadID": "t1abcde",
"runID": "r1xyz"
}
Der Lauf läuft serverseitig weiter. Das ist die richtige Wahl für Arbeit, die ein HTTP-Timeout überdauert.
Ein asynchroner Aufruf ist keine Einbahnstraße: Mit einem Lese-Scope für
Unterhaltungen – thread:read für den Agenten oder project:threads für das
Projekt der Unterhaltung – liefert GET /api/threads/{id}/events die Ereignisse
eines Laufs nach, und POST /api/threads/{id}/abort bricht ihn ab (gedeckt
durch agent:chat).
Übergeben Sie dabei die runID aus der Antwort oben. Ohne sie liefert die Route
den letzten abgeschlossenen Lauf des Threads – bei einem fortgesetzten Thread
also die Antwort des vorherigen Zugs.
curl -sS -H "Authorization: Bearer sk-bpai-1-42-…" \
-H "Accept: application/json" \
'https://your-instance.platform.basepeak.ai/api/threads/t1abcde/events?runID=r1xyz'
Mit Accept: application/json bleibt dieser Aufruf offen, bis der Lauf fertig
ist. Für Läufe, die länger dauern als ein HTTP-Timeout erlaubt, fragen Sie
stattdessen GET /api/threads/{id} ab – der Aufruf antwortet sofort und meldet
den Abschluss über lastRunID.
Details und beide Varianten vollständig: Threads.
Die Werkzeuge des Agenten einschränken
X-BPAI-Scoped-Tools beschränkt einen einzelnen Lauf auf einen Teil der
konfigurierten Werkzeuge, ohne den Agenten zu ändern:
X-BPAI-Scoped-Tools: google-calendar, gmail
Kommagetrennte Werkzeugreferenzen; Leerzeichen werden entfernt, leere Einträge verworfen. Ohne den Header gelten die Werkzeuge des Agenten – abhängig von den Credential-Scopes des Schlüssels, die Werkzeuge mit Credential aus dem Lauf entfernen können. Siehe Credential-Scopes.
Nützlich, um eine Anbindung enger zu fassen als den Agenten, den sie aufruft – etwa ein Statusseiten-Bot, der den Kalender lesen, aber keine Mails senden darf.
Innerhalb eines Projekts chatten
POST /api/invoke/{id} spricht immer mit dem nackten Agenten – ohne
geteilten Arbeitsbereich, ohne dessen Aufgaben als Werkzeuge, ohne
projektbezogene Anmeldedaten oder Modelleinstellungen. Das ist die
dünnere Oberfläche, und das ist beabsichtigt: richtig für Kopfstand-Agenten
(headless) und System-Agenten, die an keinem Projekt hängen – nicht die
Unterhaltung, die der Browser führt.
Für Letzteres gibt es das entsprechende Routenpaar auf Projektbasis:
POST /api/projects/{project_id}/invoke
POST /api/projects/{project_id}/invoke/thread/{thread_id}
Scope project:chat:<project_id> statt agent:chat:<id>; ansonsten
identisches Verhalten – derselbe Antwortkörper, derselbe Header
X-BPAI-Thread-Id, dieselben Accept-Varianten, dasselbe ?async=true. Der
Agent kommt dabei aus dem Projekt, Sie nennen ihn nicht:
curl -sS -X POST \
https://your-instance.platform.basepeak.ai/api/projects/p1abc/invoke \
-H "Authorization: Bearer sk-bpai-1-42-…" \
-H "Accept: application/json" \
-H "Content-Type: text/plain" \
--data 'Fasse die Dateien in diesem Projekt zusammen.'
Details, Scope-Tabelle und die weiteren Projekt-Routen (Dateien, Wissen, Aufgaben, Umgebungsvariablen): Projekte.
Fehler
Fehler dieser Schnittstelle verwenden das Problemformat der Plattform, nicht den Fehlerumschlag von OpenAI:
| Status | Wann |
|---|---|
400 | {id} löst zu keinem Agenten auf. |
401 | Fehlender, fehlerhafter, unbekannter oder abgelaufener Bearer. |
403 | Die Scopes deckten den Agenten nicht ab; oder das Konto des Schlüssels hat keinen Zugriff auf diesen Agenten; oder der API-Zugriff der Instanz ist deaktiviert. |
404 | Agent, Alias oder Thread existiert nicht. |
500 | Serverseitiger Fehler bei der Auflösung des Agenten oder beim Start des Laufs. |
Ein Platzhalter-Schlüssel agent:chat:* wird gegen die eigenen
Berechtigungen des zugehörigen Kontos erneut geprüft und kann daher keine
Agenten erreichen, die dieses Konto nicht im Browser öffnen könnte.
Sobald der SSE-Strom begonnen hat, kommt ein Fehler als Ereignis mit
gesetztem error und nicht als HTTP-Status – die Statuszeile war bereits
gesendet. Trennt der Client die Verbindung, bemerkt der Server dies beim
nächsten Schreiben und bricht den Lauf ab.
Beispiele
curl – mit Streaming
curl -N -sS -X POST \
https://your-instance.platform.basepeak.ai/api/invoke/a17jlm7 \
-H "Authorization: Bearer sk-bpai-1-42-…" \
-H "Accept: text/event-stream" \
-H "Content-Type: text/plain" \
--data 'Gib mir drei Stichpunkte zu den Q3-Zahlen.'
event: start
data: {}
id: r1xyz
data: {"runID":"r1xyz","threadID":"t1abcde","waitingOnModel":true,"content":""}
id: r1xyz
data: {"runID":"r1xyz","content":"- Umsatz +12 %"}
: ping
id: r1xyz:after
data: {"runID":"r1xyz","runComplete":true,"content":""}
event: close
data: {}
Python – streamen und Text ausgeben
import httpx
BASE = "https://your-instance.platform.basepeak.ai"
TOKEN = "sk-bpai-1-42-…"
AGENT = "a17jlm7"
with httpx.stream(
"POST", f"{BASE}/api/invoke/{AGENT}",
headers={
"Authorization": f"Bearer {TOKEN}",
# Genau dieser Wert – ein angehängtes */* deaktiviert SSE stillschweigend.
"Accept": "text/event-stream",
"Content-Type": "text/plain",
},
content="Fasse die heutigen Support-Tickets zusammen.",
timeout=None,
) as r:
r.raise_for_status()
thread = r.headers["x-bpai-thread-id"] # für späteres Fortsetzen merken
import json
for line in r.iter_lines():
if not line.startswith("data: "):
continue # überspringt ': ping', 'id:' und 'event:'
event = json.loads(line[6:])
if event.get("error"):
raise RuntimeError(event["error"])
if text := event.get("content"):
print(text, end="", flush=True)
if event.get("runComplete"):
break
print(f"\n\nThread: {thread}")
Python – Werkzeugaktivität anzeigen
for line in r.iter_lines():
if not line.startswith("data: "):
continue
e = json.loads(line[6:])
if e.get("waitingOnModel"):
print("[denkt nach …]")
elif tc := e.get("toolCall"):
print(f"[ruft {tc.get('name', 'Werkzeug')} auf]")
elif st := e.get("step"):
print(f"[Schritt: {st.get('description', '')}]")
elif p := e.get("prompt"):
# Meist eine OAuth-Zustimmung, die der Agent zum Weiterarbeiten braucht.
print(f"[Aktion erforderlich: {p.get('message', '')}]")
elif txt := e.get("content"):
print(txt, end="", flush=True)
Node – die gesamte Antwort als JSON einsammeln
Fordern Sie einen Umschlag statt eines Stroms an, wenn Sie nur das Ergebnis brauchen:
const res = await fetch(`${BASE}/api/invoke/${AGENT}`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
Accept: "application/json", // genau das – ohne ', */*'
"Content-Type": "text/plain",
},
body: "Was hat sich diese Woche an der Roadmap geändert?",
});
const thread = res.headers.get("x-bpai-thread-id");
const { items } = await res.json();
const answer = items.map((e) => e.content ?? "").join("");
console.log(answer, "\nThread:", thread);
Weiterführende Dokumentation
- Threads – mehrere Unterhaltungen führen
- Dateien – dem Agenten Dokumente geben
- OpenAI-kompatible API – die Alternative zum Einstecken
- API-Schlüssel – Scopes und Tokens
- Projekte – innerhalb eines Projekts chatten