Skip to content

API Reference

Selu exposes two groups of HTTP endpoints: the v1 conversation API (used by both the web app and native mobile clients) and admin-only management endpoints for agents, policies, and system updates. All endpoints require authentication via session cookie.

The v1 API is the shared contract between the Selu web app and iOS. It provides conversation management, message sending, a replayable event feed, and tool-approval decisions.

Returns basic information about the signed-in user. The web app uses this to render role-appropriate navigation without storing credentials in the browser.

Response:

{
"display_name": "Ada Lovelace",
"is_admin": false,
"language": "en",
"supports_photo_uploads": true
}
FieldTypeDescription
display_namestringUser’s display name
is_adminbooleanWhether the user has admin privileges
languagestringUser’s preferred language code (e.g. "en", "de")
supports_photo_uploadsbooleanWhether this server accepts photo attachments on messages

List conversations for the signed-in user, ordered by most recent activity first.

Query parameters:

ParameterTypeRequiredDescription
limitintegernoNumber of conversations to return (1–100, default 50)
beforestringnoOpaque cursor from next_cursor in a previous response

Response:

{
"conversations": [
{
"id": "01J...",
"channel_id": "pipe-abc",
"channel_name": "My Web Chat",
"title": "Weekend trip ideas",
"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..."
}

Response fields:

FieldTypeDescription
conversationsarrayPage of conversation summaries
next_cursorstring | omittedPass back as ?before= to fetch the next (older) page. Omitted when no older page exists.

ConversationSummary fields:

FieldTypeDescription
idstringUnique conversation ID
channel_idstringID of the pipe this conversation belongs to
channel_namestringHuman-readable pipe name
titlestring | nullUser-visible title, null if not yet set
statusstring"active" or "archived"
kindstring"regular" for user-started conversations, "schedule" for schedule-generated threads
created_atstringISO 8601 timestamp
last_activity_atstringISO 8601 timestamp of the most recent message
active_run_idstring | nullID of an in-progress run, or null

Create a new conversation.

Response: A ConversationSummary object (see above).


GET /api/v1/conversations/{conversation_id}

Section titled “GET /api/v1/conversations/{conversation_id}”

Return a full snapshot of the conversation: its metadata, messages, and recent runs. The snapshot also includes an event_cursor you should pass to GET /api/v1/events to receive only events that happened after this snapshot was taken.

Response:

{
"conversation": { "...ConversationSummary fields..." },
"messages": [
{
"id": "msg-001",
"role": "user",
"content": "What's the weather like?",
"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 fields:

FieldTypeDescription
idstringMessage ID
rolestring"user", "assistant", "tool", or "status"
contentstringText content of the message
created_atstringISO 8601 timestamp
tool_callsobject | nullTool call details when role is "assistant" and tools were invoked
tool_call_idstring | nullPresent on "tool" role messages
attachmentsobject | nullAttached files or images
compactedbooleanWhether this message was compacted during context window management

ConversationRun fields:

FieldTypeDescription
idstringRun ID
client_message_idstringIdempotency key supplied by the client
statusstringSee run statuses below
error_codestring | nullMachine-readable error code if the run failed
created_atstringISO 8601 timestamp
started_atstring | nullWhen the run started processing
completed_atstring | nullWhen the run finished

Run statuses:

StatusMeaning
queuedRun is waiting to start
runningAgent is actively processing
waiting_for_approvalPaused — waiting for the user to approve a tool call
cancellingCancellation requested, still winding down
completedFinished successfully
failedFinished with an error
cancelledCancelled before completion
interruptedStopped unexpectedly

PATCH /api/v1/conversations/{conversation_id}

Section titled “PATCH /api/v1/conversations/{conversation_id}”

Rename a conversation. The new title is user-facing only — the agent never reads it.

Request body:

{
"title": "Weekend trip ideas"
}
FieldTypeRequiredConstraints
titlestringyesNon-empty, max 120 characters

Response: The updated ConversationSummary.

Error responses:

StatusCodeMeaning
400conversation.invalid_titleTitle was empty or missing
404—Conversation not found or not owned by you

DELETE /api/v1/conversations/{conversation_id}

Section titled “DELETE /api/v1/conversations/{conversation_id}”

Delete a conversation together with all its messages, runs, events, and stored artifacts.

Error responses:

StatusCodeMeaning
404—Conversation not found or not owned by you
409conversation.run_in_progressA run is currently active — wait for it to finish before deleting

Response: HTTP 204 on success.


POST /api/v1/conversations/{conversation_id}/messages

Section titled “POST /api/v1/conversations/{conversation_id}/messages”

Send text, photos, or both and start an agent run. The endpoint is idempotent: retry the same logical message with the same client_message_id to receive the existing run instead of creating a duplicate. The request body limit is 18 MiB.

Request body:

{
"text": "What's the weather like?",
"client_message_id": "550e8400-e29b-41d4-a716-446655440000",
"attachments": [
{
"filename": "photo.jpg",
"mime_type": "image/jpeg",
"data_base64": "<base64-encoded image data>"
}
]
}
FieldTypeRequiredDescription
textstringunless photos are presentMessage text; may be empty for a photo-only message
client_message_idstringyesStable client-generated UUID; reuse it when retrying unchanged content
attachmentsarraynoUp to 10 photo attachments

Attachment fields:

FieldTypeDescription
filenamestringOriginal filename
mime_typestringimage/jpeg, image/png, image/gif, or image/webp
data_base64stringBase64-encoded image bytes

Limits:

LimitValue
Photos per message10
Per-photo decoded size2 MiB
Total decoded size12 MiB
JSON request body18 MiB

The server verifies the file signature rather than trusting only mime_type.

Error codeMeaning
conversation.invalid_photoThe signature is not a supported image type
conversation.photo_too_largeA photo exceeds 2 MiB
conversation.too_many_photosMore than 10 photos were attached
conversation.photo_commandPhotos were combined with a slash command

Response: The created ConversationRun.


POST /api/v1/conversations/{conversation_id}/feedback

Section titled “POST /api/v1/conversations/{conversation_id}/feedback”

Rate the latest completed reply in a conversation.

Request body:

{ "rating": 1 }
ValueMeaning
1Helpful
-1Not helpful

The rating is stored on the most recent turn signal and feeds the agent’s behavioral-lessons pipeline.

Responses:

StatusCodeMeaning
204—Rating saved
400conversation.invalid_feedbackrating was not 1 or -1
404conversation.feedback_unavailableNo completed reply is available to rate
409conversation.run_in_progressA run is still active; wait for it to finish

The conversation snapshot exposes the saved value as latest_turn_rating (1, -1, or null), so clients can restore the selected state after a reload.


Return the localized slash-command catalog so clients can render command pickers without requiring a client release when commands change.

Query parameters:

ParameterTypeRequiredDescription
langstringnoen or de; defaults to the signed-in user’s language

Response:

{
"commands": [
{
"command": "/remind ",
"label": "/remind",
"description": "Set a one-time reminder",
"argument_hint": "what to do + when"
},
{
"command": "/help",
"label": "/help",
"description": "Show the available commands",
"argument_hint": null
}
]
}
FieldTypeDescription
commandstringText inserted into the composer, including a trailing space when an argument is required
labelstringShort form displayed in the picker
descriptionstringLocalized one-line description
argument_hintstring | nullLocalized argument placeholder, when applicable

An authenticated Server-Sent Events stream that delivers live conversation updates to the client.

Query parameters:

ParameterTypeRequiredDescription
cursorintegeryesLast event ID the client has seen. Use the event_cursor from the conversation snapshot.

The server replays any events with an ID greater than cursor immediately on connect, then streams new events as they happen. This makes the feed reconnect-safe: if your connection drops, reconnect with the last event ID you received and you won’t miss anything.

Event envelope:

{
"id": 43,
"conversation_id": "01J...",
"run_id": "run-001",
"type": "message.delta",
"entity_id": "msg-002",
"payload": { "delta": "sunny" },
"created_at": "2026-09-03T10:00:03.000Z"
}
FieldTypeDescription
idintegerMonotonically increasing event ID. Use this as your next cursor.
conversation_idstringWhich conversation this event belongs to
run_idstring | nullThe run that produced this event, if applicable
typestringEvent type (see table below)
entity_idstring | nullID of the affected entity (message ID, run ID, etc.)
payloadobjectEvent-specific data
created_atstringISO 8601 timestamp

Event types:

TypePayloadDescription
message.created{ id, role, content, ... }A new message was added to the conversation
message.delta{ delta: string }Streaming text chunk for an in-progress assistant message
run.started{ run_id }An agent run began
run.tool_call{ name, arguments, ... }The agent called a tool
run.tool_result{ tool_call_id, content }A tool returned a result
run.approval_required{ approval_id, tool_name, ... }The run is paused waiting for your approval
run.output_finished{ run_id }The agent’s reply is complete
run.completed{ run_id, status }The run reached a terminal state
conversation.changed{ title }Conversation metadata was updated (e.g. renamed)
conversation.deleted{}The conversation was deleted (transient — not persisted)

Download an artifact using the active session. Access is owner-scoped: requesting another user’s in-memory or disk-backed artifact returns 404.

The response contains the raw file with appropriate Content-Type and Content-Disposition headers. Images are served inline; other files are served as attachment.


POST /api/v1/approvals/{approval_id}/decision

Section titled “POST /api/v1/approvals/{approval_id}/decision”

Approve or deny a pending tool call that is waiting for your confirmation.

Approval IDs are scoped to their owner. Attempting to decide an approval you don’t own returns 404 without consuming the pending approval.

Request body:

{
"approved": true
}
FieldTypeRequiredDescription
approvedbooleanyestrue to allow the tool call, false to block it

Response: HTTP 200 on success.


The following endpoints require the signed-in user to have is_admin = true. Non-admin requests return HTTP 403.

Start an agent update in the background.

Request:

POST /agents/update/start
Content-Type: application/x-www-form-urlencoded
entry_json={"id":"weather","name":"Weather Assistant",...}
ParameterTypeDescription
entry_jsonstringJSON-encoded marketplace entry for the agent to update

Response:

{ "job_id": "550e8400-e29b-41d4-a716-446655440000" }

Poll the status of a background agent update job.

Response:

{
"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
}
FieldTypeDescription
progressnumberCompletion percentage (0–100)
message_keystringi18n key for the current status message
donebooleanWhether the job has finished
successbooleanWhether it finished successfully (only meaningful when done is true)
redirect_tostring | nullURL to navigate to after completion
error_keystring | nulli18n key for the error message if the job failed

Status message keys:

KeyMeaning
agents.update.phase.preparingJob is starting
agents.update.phase.downloadingDownloading Docker images
agents.update.success.updatedUpdate completed
agents.update.success.setup_requiredUpdate completed, setup required
agents.update.error.dockerDocker connection failed
agents.update.error.startCould not start the update
agents.update.error.failedUpdate process failed

Synchronous update endpoint. Still available for form-based flows but may block for several minutes.

Response: HTTP 302 redirect to the agents page or setup wizard.

Update a tool policy for a specific agent.

ParameterLocationDescription
agent_idpathAgent identifier
capability_idformCapability ID; use __builtin__ for built-in tools
tool_nameformTool name
policyformallow, ask, or block
scopeformglobal (admin only) or user

Response: HTTP 200 (empty body) on success.

Reset a user’s personal policy override back to the global default.

ParameterLocationDescription
agent_idpathAgent identifier
capability_idformCapability ID
tool_nameformTool name
return_toformOptional URL to redirect to after reset

Response: HTTP 200 with HX-Redirect header pointing to the agent permissions page.

Display the system update management page.

Change the release channel.

ParameterTypeValues
release_channelstringstable, dev, or nightly

Response: HTTP 302 redirect.

Manually check for available updates.

Apply an available update.

Roll back to the previous version.

Returns the current system update status for live UI polling.

Response:

{
"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
}

Progress keys:

KeyMeaning
updates.progress.checkingFetching release metadata
updates.progress.downloadingDownloading the new Docker image
updates.progress.restartingRestarting with the new version
updates.progress.health_checkRunning post-update validation
updates.progress.apply_doneUpdate completed successfully

Toggle automatic updates.

ParameterTypeValues
auto_updatestring1 to enable, 0 to disable

Starting a conversation and streaming the response

Section titled “Starting a conversation and streaming the response”
  1. POST /api/v1/conversations — create a conversation, note its id
  2. GET /api/v1/conversations/{id} — fetch the snapshot, note event_cursor
  3. GET /api/v1/events?cursor={event_cursor} — open the SSE stream
  4. POST /api/v1/conversations/{id}/messages — send a message with a fresh client_message_id
  5. Listen on the SSE stream for message.delta chunks, run.output_finished, and run.completed
  1. You receive a run.approval_required event on the SSE stream
  2. Show the user the approval card with the tool name and arguments
  3. POST /api/v1/approvals/{approval_id}/decision with { "approved": true/false }
  4. The run resumes and the SSE stream continues

Keep track of the last event id you received from the SSE stream. On reconnect:

  1. GET /api/v1/conversations/{id} to refresh the snapshot (catches up on any messages you missed)
  2. GET /api/v1/events?cursor={last_seen_id} to resume the stream from where you left off
  1. GET /api/v1/conversations — first page (most recent 50 conversations)
  2. If the response includes next_cursor, pass it as ?before={next_cursor} to fetch the next page
  3. Repeat until next_cursor is absent
  1. POST /agents/update/start — returns job_id
  2. GET /agents/update/status/{job_id} — poll every 1–2 seconds until done: true
  3. Check success and follow redirect_to if present

All endpoints require a valid session cookie obtained from signing in at /login.

Admin endpoints additionally require is_admin = true on the user account. Non-admin requests to admin endpoints return HTTP 403.