Zum Hauptinhalt springen

Invoke-API

Premium-Funktion

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:

FormBeispielVerhalten
Agenten-IDa17jlm7Führt diesen Agenten aus.
Agenten-Aliassupport-botWird zum Agenten hinter dem Alias aufgelöst.
Thread-IDt1abcdeFührt den Agenten des Threads aus und setzt diesen Thread fort.
Der Scope leitet sich aus dem Pfad ab, nicht aus einem Header

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}
WegVorrang
Pfadsegment {thread}Gewinnt, wenn vorhanden.
Header X-BPAI-Thread-IdWird verwendet, wenn der Pfad keinen Thread trägt.
Keines von beidemEin 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.

HeaderErforderlichZweck
AuthorizationjaBearer sk-bpai-<user>-<key>-<secret>
AcceptneinWählt das Antwortformat – siehe unten.
X-BPAI-Thread-IdneinEinen Thread fortsetzen.
X-BPAI-Scoped-ToolsneinDen Lauf auf einen Teil der Werkzeuge beschränken.
X-BPAI-Run-ModeneinEinen Modus für diesen Lauf zuschalten, etwa research.

Query-Parameter​

ParameterStandardBedeutung
asyncfalsetrue 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:

AcceptAntwort
text/event-streamSSE-Strom der Ereignisse, live während des Laufs.
application/jsonEin JSON-Umschlag {"items":[…]} nach Ende des Laufs.
alles andere oder nicht gesetztReiner Text: das content jedes Ereignisses, aneinandergefügt und gestreamt.
Accept wird exakt verglichen – */* bricht das Streaming

Der 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:

FeldBedeutung
contentAusgabetext. Über alle Ereignisse aneinanderfügen, um die Antwort zu erhalten.
contentIDKennzeichnet einen Inhaltsstrom, sodass Fragmente zugeordnet werden können.
runID / threadIDZu welchem Lauf und Thread das Ereignis gehört.
runCompletetrue beim letzten Ereignis des Laufs.
errorGesetzt, wenn der Lauf gescheitert ist.
timeZeitpunkt der Entstehung.

Weitere Felder, von denen je Ereignis höchstens eines gesetzt ist:

FeldBedeutung
waitingOnModelDas Modell hat noch nicht begonnen zu antworten – gut für einen Ladeindikator.
toolInputDas Modell stellt Werkzeugargumente zusammen (kann dauern).
toolCallEin Werkzeug wird aufgerufen.
stepDer aktuelle Schritt hat gewechselt; folgende Ereignisse gehören dazu.
promptDer Agent benötigt eine Eingabe – meist eine OAuth-Anmeldung. Enthält fields, message und ein Kennzeichen sensitive.
inputEingabe, die dem Lauf übergeben wurde.
replayCompleteAlle bereits vorhandenen Ereignisse sind ausgeliefert; ab hier folgt Neues.
usernameWer 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.

Asynchrone Läufe abfragen

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.

Einen Modus für einen einzelnen Lauf zuschalten​

X-BPAI-Run-Mode schaltet für einen einzelnen Lauf einen Modus zu und stellt dem Agenten dessen Werkzeuge zusätzlich zu seinen eigenen bereit:

X-BPAI-Run-Mode: research

Das ist das Gegenstück zu X-BPAI-Scoped-Tools: ein Modus erweitert, wo Scoping nur einschränken kann. Kommagetrennt, Leerzeichen werden entfernt.

ModusWas er zuschaltetErfordert
researchTiefenrecherche – plant Teilfragen, durchsucht das Web, liest Quellen und liefert einen belegten Bericht. Läuft bis zu 30 Minuten.Recherche-Modus

Sie senden einen Modusnamen, keine Werkzeugreferenz. Welche Werkzeuge ein Modus mitbringt, entscheidet der Server – ein Aufruf kann sich also nicht ein beliebiges Werkzeug an einen Agenten hängen.

Modi, für die Ihr Arbeitsbereich nicht freigeschaltet ist, und Namen, die der Server nicht kennt, werden ignoriert statt abgelehnt: Der Lauf startet dann ganz normal, ohne den Modus. Sie verlieren also keine Nachricht, wenn eine Integration einen Modus anfragt, den es hier nicht gibt – prüfen Sie die Freischaltung, wenn ein Lauf unerwartet ohne Recherche antwortet.

Die Werkzeuge eines Modus brauchen weiterhin die Credential-Scopes des Schlüssels

Ein Modus hängt seine Werkzeuge an – und ein angehängtes Werkzeug wird danach genauso geprüft wie ein am Agenten konfiguriertes, also auch gegen die Credential-Scopes Ihres Schlüssels. research hängt die Tiefenrecherche an, die das Credential search verwendet. Einem Schlüssel ohne credential:use:search (oder credential:use:*) wird dieses Werkzeug daher wieder entfernt, und der Lauf antwortet ohne Recherche.

Das ist die Credential-Schranke, die ihre Arbeit tut – kein Fehler – und sie schweigt bewusst. Wenn X-BPAI-Run-Mode: research über einen API-Schlüssel scheinbar nichts tut, im Browser aber funktioniert, prüfen Sie das zuerst. Siehe Credential-Scopes.

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:

StatusWann
400{id} löst zu keinem Agenten auf.
401Fehlender, fehlerhafter, unbekannter oder abgelaufener Bearer.
403Die 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.
404Agent, Alias oder Thread existiert nicht.
500Serverseitiger 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​