API-Referenz
Selu bietet zwei Gruppen von HTTP-Endpunkten: die v1 Conversation API (genutzt von der Web-App und nativen mobilen Clients) sowie Admin-Endpunkte für die Verwaltung von Agenten, Richtlinien und System-Updates. Alle Endpunkte erfordern Authentifizierung über Session-Cookie.
v1 Conversation API
Abschnitt betitelt „v1 Conversation API“Die v1 API ist der gemeinsame Vertrag zwischen der Selu-Web-App und iOS. Sie umfasst Unterhaltungsverwaltung, Nachrichtenversand, einen wiederholbaren Event-Feed und Tool-Genehmigungsentscheidungen.
GET /api/v1/session
Abschnitt betitelt „GET /api/v1/session“Gibt grundlegende Informationen über den angemeldeten Benutzer zurück. Die Web-App nutzt dies, um eine rollengerechte Navigation anzuzeigen, ohne Zugangsdaten im Browser zu speichern.
Antwort:
{ "display_name": "Ada Lovelace", "is_admin": false, "language": "de", "supports_photo_uploads": true}| Feld | Typ | Beschreibung |
|---|---|---|
display_name | string | Anzeigename des Benutzers |
is_admin | boolean | Ob der Benutzer Admin-Rechte hat |
language | string | Bevorzugter Sprachcode (z. B. "en", "de") |
supports_photo_uploads | boolean | Ob dieser Server Foto-Anhänge in Nachrichten akzeptiert |
GET /api/v1/conversations
Abschnitt betitelt „GET /api/v1/conversations“Listet Unterhaltungen des angemeldeten Benutzers, sortiert nach zuletzt aktiv.
Query-Parameter:
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
limit | integer | nein | Anzahl zurückzugebender Unterhaltungen (1–100, Standard 50) |
before | string | nein | Opaker Cursor aus next_cursor einer vorherigen Antwort |
Antwort:
{ "conversations": [ { "id": "01J...", "channel_id": "pipe-abc", "channel_name": "Mein Web-Chat", "title": "Ideen für das Wochenende", "status": "active", "kind": "regular", "created_at": "2026-09-03T10:00:00.000Z", "last_activity_at": "2026-09-03T10:15:00.000Z", "active_run_id": null } ], "next_cursor": "2026-09-03T10:00:00.000Z|01J..."}Antwort-Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
conversations | array | Seite mit Unterhaltungszusammenfassungen |
next_cursor | string | nicht vorhanden | Als ?before= übergeben, um die nächste (ältere) Seite abzurufen. Fehlt, wenn keine ältere Seite existiert. |
ConversationSummary-Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
id | string | Eindeutige Unterhaltungs-ID |
channel_id | string | ID der Pipe, zu der diese Unterhaltung gehört |
channel_name | string | Lesbarer Pipe-Name |
title | string | null | Benutzer-sichtbarer Titel, null wenn noch nicht gesetzt |
status | string | "active" oder "archived" |
kind | string | "regular" für vom Benutzer gestartete Unterhaltungen, "schedule" für durch Zeitpläne erzeugte Threads |
created_at | string | ISO 8601 Zeitstempel |
last_activity_at | string | ISO 8601 Zeitstempel der letzten Nachricht |
active_run_id | string | null | ID eines laufenden Runs, oder null |
POST /api/v1/conversations
Abschnitt betitelt „POST /api/v1/conversations“Erstellt eine neue Unterhaltung.
Antwort: Ein ConversationSummary-Objekt (siehe oben).
GET /api/v1/conversations/{conversation_id}
Abschnitt betitelt „GET /api/v1/conversations/{conversation_id}“Gibt einen vollständigen Snapshot der Unterhaltung zurück: Metadaten, Nachrichten und aktuelle Runs. Der Snapshot enthält außerdem einen event_cursor, den du an GET /api/v1/events übergeben solltest, um nur Events zu empfangen, die nach diesem Snapshot eingetreten sind.
Antwort:
{ "conversation": { "...ConversationSummary-Felder..." }, "messages": [ { "id": "msg-001", "role": "user", "content": "Wie ist das Wetter?", "created_at": "2026-09-03T10:00:00.000Z", "tool_calls": null, "tool_call_id": null, "attachments": null, "compacted": false } ], "runs": [ { "id": "run-001", "client_message_id": "client-uuid", "status": "completed", "error_code": null, "created_at": "2026-09-03T10:00:00.000Z", "started_at": "2026-09-03T10:00:01.000Z", "completed_at": "2026-09-03T10:00:05.000Z" } ], "event_cursor": 42}ConversationMessage-Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
id | string | Nachrichten-ID |
role | string | "user", "assistant", "tool" oder "status" |
content | string | Textinhalt der Nachricht |
created_at | string | ISO 8601 Zeitstempel |
tool_calls | object | null | Tool-Aufruf-Details, wenn die Rolle "assistant" ist und Tools aufgerufen wurden |
tool_call_id | string | null | Vorhanden bei Nachrichten mit Rolle "tool" |
attachments | object | null | Angehängte Dateien oder Bilder |
compacted | boolean | Ob diese Nachricht beim Context-Window-Management komprimiert wurde |
ConversationRun-Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
id | string | Run-ID |
client_message_id | string | Idempotency-Schlüssel vom Client |
status | string | Siehe Run-Status unten |
error_code | string | null | Maschinenlesbarer Fehlercode bei fehlgeschlagenem Run |
created_at | string | ISO 8601 Zeitstempel |
started_at | string | null | Wann die Verarbeitung begann |
completed_at | string | null | Wann der Run abgeschlossen wurde |
Run-Status:
| Status | Bedeutung |
|---|---|
queued | Run wartet auf Start |
running | Agent verarbeitet aktiv |
waiting_for_approval | Pausiert – wartet auf Tool-Genehmigung |
cancelling | Abbruch angefordert, läuft noch aus |
completed | Erfolgreich abgeschlossen |
failed | Mit Fehler beendet |
cancelled | Vor Abschluss abgebrochen |
interrupted | Unerwartet gestoppt |
PATCH /api/v1/conversations/{conversation_id}
Abschnitt betitelt „PATCH /api/v1/conversations/{conversation_id}“Benennt eine Unterhaltung um. Der neue Titel ist nur für den Benutzer sichtbar – der Agent liest ihn nicht.
Anfrage-Body:
{ "title": "Ideen für das Wochenende"}| Feld | Typ | Pflicht | Einschränkungen |
|---|---|---|---|
title | string | ja | Nicht leer, max. 120 Zeichen |
Antwort: Das aktualisierte ConversationSummary.
Fehler-Antworten:
| Status | Code | Bedeutung |
|---|---|---|
| 400 | conversation.invalid_title | Titel war leer oder fehlend |
| 404 | — | Unterhaltung nicht gefunden oder nicht dein Besitz |
DELETE /api/v1/conversations/{conversation_id}
Abschnitt betitelt „DELETE /api/v1/conversations/{conversation_id}“Löscht eine Unterhaltung zusammen mit allen Nachrichten, Runs, Events und gespeicherten Artefakten.
Fehler-Antworten:
| Status | Code | Bedeutung |
|---|---|---|
| 404 | — | Unterhaltung nicht gefunden oder nicht dein Besitz |
| 409 | conversation.run_in_progress | Ein Run ist gerade aktiv – warte auf dessen Abschluss vor dem Löschen |
Antwort: HTTP 204 bei Erfolg.
POST /api/v1/conversations/{conversation_id}/messages
Abschnitt betitelt „POST /api/v1/conversations/{conversation_id}/messages“Sendet Text, Fotos oder beides und startet einen Agenten-Run. Der Endpunkt ist idempotent: Wiederhole dieselbe logische Nachricht mit derselben client_message_id, um den bestehenden Run zu erhalten, statt ein Duplikat zu erstellen. Das Limit für den Request-Body beträgt 18 MiB.
Anfrage-Body:
{ "text": "Wie ist das Wetter?", "client_message_id": "550e8400-e29b-41d4-a716-446655440000", "attachments": [ { "filename": "foto.jpg", "mime_type": "image/jpeg", "data_base64": "<base64-kodierte Bilddaten>" } ]}| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
text | string | außer bei Fotos | Nachrichtentext; darf bei einer reinen Foto-Nachricht leer sein |
client_message_id | string | ja | Stabile, clientseitig erzeugte UUID; bei unverändertem Inhalt für Wiederholungen erneut verwenden |
attachments | array | nein | Bis zu 10 Foto-Anhänge |
Anhang-Felder:
| Feld | Typ | Beschreibung |
|---|---|---|
filename | string | Originaldateiname |
mime_type | string | image/jpeg, image/png, image/gif oder image/webp |
data_base64 | string | Base64-kodierte Bildbytes |
Limits:
| Limit | Wert |
|---|---|
| Fotos pro Nachricht | 10 |
| Dekodierte Größe pro Foto | 2 MiB |
| Dekodierte Gesamtgröße | 12 MiB |
| JSON-Request-Body | 18 MiB |
Der Server prüft die Dateisignatur, statt nur mime_type zu vertrauen.
| Fehlercode | Bedeutung |
|---|---|
conversation.invalid_photo | Die Signatur gehört zu keinem unterstützten Bildformat |
conversation.photo_too_large | Ein Foto überschreitet 2 MiB |
conversation.too_many_photos | Mehr als 10 Fotos wurden angehängt |
conversation.photo_command | Fotos wurden mit einem Slash-Befehl kombiniert |
Antwort: Der erstellte ConversationRun.
POST /api/v1/conversations/{conversation_id}/feedback
Abschnitt betitelt „POST /api/v1/conversations/{conversation_id}/feedback“Bewertet die letzte abgeschlossene Antwort in einer Unterhaltung.
Anfrage-Body:
{ "rating": 1 }| Wert | Bedeutung |
|---|---|
1 | Hilfreich |
-1 | Nicht hilfreich |
Die Bewertung wird im letzten Turn-Signal gespeichert und fließt in die Verhaltenslektionen des Agenten ein.
Antworten:
| Status | Code | Bedeutung |
|---|---|---|
204 | — | Bewertung gespeichert |
400 | conversation.invalid_feedback | rating war weder 1 noch -1 |
404 | conversation.feedback_unavailable | Es ist noch keine abgeschlossene Antwort zum Bewerten vorhanden |
409 | conversation.run_in_progress | Ein Run ist noch aktiv; warte auf seinen Abschluss |
Der Unterhaltungs-Snapshot stellt den gespeicherten Wert als latest_turn_rating (1, -1 oder null) bereit, damit Clients die Auswahl nach einem Neuladen wiederherstellen können.
GET /api/v1/commands
Abschnitt betitelt „GET /api/v1/commands“Gibt den lokalisierten Slash-Befehls-Katalog zurück. Clients können damit eine Befehlsauswahl darstellen, ohne bei neuen oder geänderten Befehlen veröffentlicht werden zu müssen.
Query-Parameter:
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
lang | string | nein | en oder de; standardmäßig die Sprache des angemeldeten Benutzers |
Antwort:
{ "commands": [ { "command": "/remind ", "label": "/remind", "description": "Eine einmalige Erinnerung setzen", "argument_hint": "was zu tun ist + wann" }, { "command": "/help", "label": "/help", "description": "Verfügbare Befehle anzeigen", "argument_hint": null } ]}| Feld | Typ | Beschreibung |
|---|---|---|
command | string | Text für das Eingabefeld, einschließlich Leerzeichen am Ende, wenn ein Argument erforderlich ist |
label | string | Kurzform in der Befehlsauswahl |
description | string | Lokalisierte einzeilige Beschreibung |
argument_hint | string | null | Lokalisierter Platzhalter für das Argument, falls erforderlich |
GET /api/v1/events
Abschnitt betitelt „GET /api/v1/events“Ein authentifizierter Server-Sent Events-Stream, der Live-Updates an den Client liefert.
Query-Parameter:
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
cursor | integer | ja | Letzte Event-ID, die der Client gesehen hat. Verwende event_cursor aus dem Unterhaltungs-Snapshot. |
Der Server spielt beim Verbindungsaufbau sofort alle Events mit einer ID größer als cursor nach, dann werden neue Events live gestreamt. Das macht den Feed reconnect-sicher: bricht die Verbindung ab, verbinde dich mit der zuletzt empfangenen Event-ID neu und du verpasst nichts.
Event-Envelope:
{ "id": 43, "conversation_id": "01J...", "run_id": "run-001", "type": "message.delta", "entity_id": "msg-002", "payload": { "delta": "sonnig" }, "created_at": "2026-09-03T10:00:03.000Z"}| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Monoton steigend Event-ID. Verwende sie als nächsten Cursor. |
conversation_id | string | Zu welcher Unterhaltung dieses Event gehört |
run_id | string | null | Der Run, der dieses Event erzeugt hat, falls zutreffend |
type | string | Event-Typ (siehe Tabelle unten) |
entity_id | string | null | ID der betroffenen Entität (Nachrichten-ID, Run-ID usw.) |
payload | object | Event-spezifische Daten |
created_at | string | ISO 8601 Zeitstempel |
Event-Typen:
| Typ | Payload | Beschreibung |
|---|---|---|
message.created | { id, role, content, ... } | Eine neue Nachricht wurde zur Unterhaltung hinzugefügt |
message.delta | { delta: string } | Streaming-Textchunk einer laufenden Agenten-Antwort |
run.started | { run_id } | Ein Agenten-Run hat begonnen |
run.tool_call | { name, arguments, ... } | Der Agent hat ein Tool aufgerufen |
run.tool_result | { tool_call_id, content } | Ein Tool hat ein Ergebnis zurückgegeben |
run.approval_required | { approval_id, tool_name, ... } | Der Run wartet auf deine Genehmigung |
run.output_finished | { run_id } | Die Agenten-Antwort ist vollständig |
run.completed | { run_id, status } | Der Run hat einen Endzustand erreicht |
conversation.changed | { title } | Unterhaltungsmetadaten wurden aktualisiert (z. B. umbenannt) |
conversation.deleted | {} | Die Unterhaltung wurde gelöscht (transient – nicht persistiert) |
GET /api/v1/artifacts/{artifact_id}
Abschnitt betitelt „GET /api/v1/artifacts/{artifact_id}“Lädt ein Artefakt mit der aktiven Sitzung herunter. Der Zugriff ist auf den Eigentümer beschränkt: Die Anfrage für ein fremdes Artefakt aus dem Arbeitsspeicher oder von der Festplatte gibt 404 zurück.
Die Antwort enthält die Rohdatei mit passenden Content-Type- und Content-Disposition-Headern. Bilder werden inline ausgeliefert, andere Dateien als attachment.
POST /api/v1/approvals/{approval_id}/decision
Abschnitt betitelt „POST /api/v1/approvals/{approval_id}/decision“Genehmigt oder verweigert einen ausstehenden Tool-Aufruf, der auf deine Bestätigung wartet.
Genehmigungs-IDs sind auf ihren Besitzer beschränkt. Ein Versuch, eine Genehmigung eines anderen Benutzers zu entscheiden, gibt 404 zurück, ohne die ausstehende Genehmigung zu verbrauchen.
Anfrage-Body:
{ "approved": true}| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
approved | boolean | ja | true um den Tool-Aufruf zu erlauben, false um ihn zu blockieren |
Antwort: HTTP 200 bei Erfolg.
Admin-Endpunkte
Abschnitt betitelt „Admin-Endpunkte“Die folgenden Endpunkte erfordern is_admin = true beim angemeldeten Benutzer. Anfragen ohne Admin-Rechte geben HTTP 403 zurück.
Agenten-Verwaltung
Abschnitt betitelt „Agenten-Verwaltung“POST /agents/update/start
Abschnitt betitelt „POST /agents/update/start“Startet ein Agenten-Update im Hintergrund.
Anfrage:
POST /agents/update/startContent-Type: application/x-www-form-urlencoded
entry_json={"id":"weather","name":"Weather Assistant",...}| Parameter | Typ | Beschreibung |
|---|---|---|
entry_json | string | JSON-kodierter Marketplace-Eintrag des zu aktualisierenden Agenten |
Antwort:
{ "job_id": "550e8400-e29b-41d4-a716-446655440000" }GET /agents/update/status/{job_id}
Abschnitt betitelt „GET /agents/update/status/{job_id}“Fragt den Status eines Hintergrund-Update-Jobs ab.
Antwort:
{ "job_id": "550e8400-e29b-41d4-a716-446655440000", "agent_id": "weather", "agent_name": "Weather Assistant", "target_version": "1.2.0", "progress": 75, "message_key": "agents.update.phase.downloading", "done": false, "success": false, "redirect_to": null, "error_key": null}| Feld | Typ | Beschreibung |
|---|---|---|
progress | number | Fertigstellungsgrad in Prozent (0–100) |
message_key | string | i18n-Schlüssel für die aktuelle Statusmeldung |
done | boolean | Ob der Job abgeschlossen ist |
success | boolean | Ob er erfolgreich abgeschlossen wurde (nur relevant wenn done true ist) |
redirect_to | string | null | URL zum Weiterleiten nach Abschluss |
error_key | string | null | i18n-Schlüssel für die Fehlermeldung bei Fehler |
Status-Schlüssel:
| Schlüssel | Bedeutung |
|---|---|
agents.update.phase.preparing | Job startet |
agents.update.phase.downloading | Docker-Images werden heruntergeladen |
agents.update.success.updated | Update abgeschlossen |
agents.update.success.setup_required | Update abgeschlossen, Setup erforderlich |
agents.update.error.docker | Docker-Verbindung fehlgeschlagen |
agents.update.error.start | Update konnte nicht gestartet werden |
agents.update.error.failed | Update-Prozess fehlgeschlagen |
POST /agents/update (legacy)
Abschnitt betitelt „POST /agents/update (legacy)“Synchroner Update-Endpunkt. Noch verfügbar für formularbasierte Flows, kann aber mehrere Minuten blockieren.
Antwort: HTTP 302 Weiterleitung zur Agenten-Seite oder zum Setup-Assistenten.
Tool-Richtlinien-Verwaltung
Abschnitt betitelt „Tool-Richtlinien-Verwaltung“POST /agents/{agent_id}/policy
Abschnitt betitelt „POST /agents/{agent_id}/policy“Aktualisiert eine Tool-Richtlinie für einen bestimmten Agenten.
| Parameter | Ort | Beschreibung |
|---|---|---|
agent_id | Pfad | Agenten-Bezeichner |
capability_id | Formular | Capability-ID; __builtin__ für eingebaute Tools verwenden |
tool_name | Formular | Tool-Name |
policy | Formular | allow, ask oder block |
scope | Formular | global (nur Admin) oder user |
Antwort: HTTP 200 (leerer Body) bei Erfolg.
POST /agents/{agent_id}/policy/reset
Abschnitt betitelt „POST /agents/{agent_id}/policy/reset“Setzt eine persönliche Richtlinien-Überschreibung auf den globalen Standard zurück.
| Parameter | Ort | Beschreibung |
|---|---|---|
agent_id | Pfad | Agenten-Bezeichner |
capability_id | Formular | Capability-ID |
tool_name | Formular | Tool-Name |
return_to | Formular | Optionale Weiterleitungs-URL nach dem Reset |
Antwort: HTTP 200 mit HX-Redirect-Header zur Agenten-Berechtigungsseite.
System-Updates
Abschnitt betitelt „System-Updates“GET /system-updates
Abschnitt betitelt „GET /system-updates“Zeigt die System-Update-Verwaltungsseite an.
POST /system-updates/channel
Abschnitt betitelt „POST /system-updates/channel“Ändert den Release-Kanal.
| Parameter | Typ | Werte |
|---|---|---|
release_channel | string | stable, dev oder nightly |
Antwort: HTTP 302 Weiterleitung.
POST /system-updates/check
Abschnitt betitelt „POST /system-updates/check“Prüft manuell auf verfügbare Updates.
POST /system-updates/apply
Abschnitt betitelt „POST /system-updates/apply“Wendet ein verfügbares Update an.
POST /system-updates/rollback
Abschnitt betitelt „POST /system-updates/rollback“Kehrt zur vorherigen Version zurück.
GET /system-updates/job-status
Abschnitt betitelt „GET /system-updates/job-status“Gibt den aktuellen System-Update-Status für Live-Polling zurück.
Antwort:
{ "active_job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "updating", "progress_key": "updates.progress.downloading", "installed_version": "v1.5.0", "installed_display": "v1.5.0 (build 123)", "available_version": "v1.5.1", "available_display": "v1.5.1 (build 124)", "available_changelog_url": "https://selu.bot/changelog/v1.5.1", "update_available": true, "rollback_available": true}Fortschritts-Schlüssel:
| Schlüssel | Bedeutung |
|---|---|
updates.progress.checking | Release-Metadaten werden abgerufen |
updates.progress.downloading | Neues Docker-Image wird heruntergeladen |
updates.progress.restarting | Neustart mit neuer Version |
updates.progress.health_check | Post-Update-Validierung läuft |
updates.progress.apply_done | Update erfolgreich abgeschlossen |
POST /system-updates/auto-update
Abschnitt betitelt „POST /system-updates/auto-update“Schaltet automatische Updates ein oder aus.
| Parameter | Typ | Werte |
|---|---|---|
auto_update | string | 1 zum Aktivieren, 0 zum Deaktivieren |
Typische Abläufe
Abschnitt betitelt „Typische Abläufe“Unterhaltung starten und Antwort streamen
Abschnitt betitelt „Unterhaltung starten und Antwort streamen“POST /api/v1/conversations— Unterhaltung erstellen,idnotierenGET /api/v1/conversations/{id}— Snapshot abrufen,event_cursornotierenGET /api/v1/events?cursor={event_cursor}— SSE-Stream öffnenPOST /api/v1/conversations/{id}/messages— Nachricht mit frischerclient_message_idsenden- Im SSE-Stream auf
message.delta-Chunks,run.output_finishedundrun.completedhören
Tool-Genehmigung verarbeiten
Abschnitt betitelt „Tool-Genehmigung verarbeiten“- Du erhältst ein
run.approval_required-Event im SSE-Stream - Zeige dem Benutzer die Genehmigungskarte mit Tool-Name und Argumenten
POST /api/v1/approvals/{approval_id}/decisionmit{ "approved": true/false }- Der Run wird fortgesetzt und der SSE-Stream läuft weiter
Nach Verbindungsabbruch neu verbinden
Abschnitt betitelt „Nach Verbindungsabbruch neu verbinden“Merke dir die letzte empfangene Event-id aus dem SSE-Stream. Beim Neuverbinden:
GET /api/v1/conversations/{id}— Snapshot aktualisieren (holt verpasste Nachrichten nach)GET /api/v1/events?cursor={zuletzt_gesehene_id}— Stream dort fortsetzen, wo du aufgehört hast
Unterhaltungsliste paginieren
Abschnitt betitelt „Unterhaltungsliste paginieren“GET /api/v1/conversations— erste Seite (aktuellste 50 Unterhaltungen)- Wenn die Antwort
next_cursorenthält, als?before={next_cursor}übergeben - Wiederholen bis
next_cursorfehlt
Agenten-Update-Ablauf
Abschnitt betitelt „Agenten-Update-Ablauf“POST /agents/update/start— gibtjob_idzurückGET /agents/update/status/{job_id}— alle 1–2 Sekunden abfragen bisdone: truesuccessprüfen undredirect_tofolgen, falls vorhanden
Authentifizierung
Abschnitt betitelt „Authentifizierung“Alle Endpunkte erfordern ein gültiges Session-Cookie, das beim Anmelden unter /login gesetzt wird.
Admin-Endpunkte erfordern zusätzlich is_admin = true auf dem Benutzerkonto. Nicht-Admin-Anfragen an Admin-Endpunkte geben HTTP 403 zurück.