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.

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.

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