Backend-Controlled, UI-Compatible API Flow
This tutorial is a community contribution and is not supported by the Open WebUI team. It serves only as a demonstration on how to customize Open WebUI for your specific use case. Want to contribute? Check out the contributing tutorial.
Backend-Controlled, UI-Compatible API Flow
This tutorial demonstrates how to implement server-side orchestration of Open WebUI conversations while ensuring that assistant replies appear properly in the frontend UI. This approach requires zero frontend involvement and allows complete backend control over the chat flow. This tutorial has been verified to work with Open WebUI version v0.6.15. Future versions may introduce changes in behavior or API structure.
Prerequisites
Before following this tutorial, ensure you have:
- A running Open WebUI instance
- Valid API authentication token
- Access to the Open WebUI backend APIs
- Basic understanding of REST APIs and JSON
- Command-line tools:
curl,jq(optional for JSON parsing)
Overview
This tutorial describes a comprehensive 7-step process that enables server-side orchestration of Open WebUI conversations while ensuring that assistant replies appear properly in the frontend UI.
Process Flow
The essential steps are:
- Create a new chat with a user message - Initialize the conversation with the user's input
- Enrich the chat response with an assistant message - Add assistant message to the response object in memory
- Update chat with assistant message - Send the enriched chat state to the server
- Trigger the assistant completion - Generate the actual AI response (with optional knowledge integration)
- Wait for response completion - Monitor the assistant response until fully generated
- Complete the assistant message - Mark the response as completed
- Fetch and process the final chat - Retrieve and parse the completed conversation
This enables server-side orchestration while still making replies show up in the frontend UI exactly as if they were generated through normal user interaction.
Implementation Guide
Critical Step: Enrich Chat Response with Assistant Message
The assistant message needs to be added to the chat response object in memory as a critical prerequisite before triggering the completion. This step is essential because the Open WebUI frontend expects assistant messages to exist in a specific structure.
The assistant message must appear in both locations:
chat.messages[]- The main message arraychat.history.messages[<assistantId>]- The indexed message history
Expected structure of the assistant message:
{
"id": "<uuid>",
"role": "assistant",
"content": "",
"parentId": "<user-msg-id>",
"modelName": "gpt-4o",
"modelIdx": 0,
"timestamp": "<currentTimestamp>"
}
Without this enrichment, the assistant's response will not appear in the frontend interface, even if the completion is successful.
Step-by-Step Implementation
Step 1: Create Chat with User Message
This starts the chat and returns a chatId that will be used in subsequent requests.
curl -X POST https://<host>/api/v1/chats/new \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"chat": {
"title": "New Chat",
"models": ["gpt-4o"],
"messages": [
{
"id": "user-msg-id",
"role": "user",
"content": "Hi, what is the capital of France?",
"timestamp": 1720000000000,
"models": ["gpt-4o"]
}
],
"history": {
"current_id": "user-msg-id",
"messages": {
"user-msg-id": {
"id": "user-msg-id",
"role": "user",
"content": "Hi, what is the capital of France?",
"timestamp": 1720000000000,
"models": ["gpt-4o"]
}
}
}
}
}'
Step 2: Enrich Chat Response with Assistant Message
Add the assistant message to the chat response object in memory. Note that this can be combined with Step 1 by including the assistant message in the initial chat creation:
// Example implementation in Java
public void enrichChatWithAssistantMessage(OWUIChatResponse chatResponse, String model) {
OWUIMessage assistantOWUIMessage = buildAssistantMessage(chatResponse, model, "assistant", "");
assistantOWUIMessage.setParentId(chatResponse.getChat().getMessages().get(0).getId());
chatResponse.getChat().getMessages().add(assistantOWUIMessage);
chatResponse.getChat().getHistory().getMessages().put(assistantOWUIMessage.getId(), assistantOWUIMessage);
}
Note: This step can be performed in memory on the response object, or combined with Step 1 by including both user and empty assistant messages in the initial chat creation.
Step 3: Update Chat with Assistant Message
Send the enriched chat state containing both user and assistant messages to the server:
curl -X POST https://<host>/api/v1/chats/<chatId> \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"chat": {
"id": "<chatId>",
"title": "New Chat",
"models": ["gpt-4o"],
"messages": [
{
"id": "user-msg-id",
"role": "user",
"content": "Hi, what is the capital of France?",
"timestamp": 1720000000000,
"models": ["gpt-4o"]
},
{
"id": "assistant-msg-id",
"role": "assistant",
"content": "",
"parentId": "user-msg-id",
"modelName": "gpt-4o",
"modelIdx": 0,
"timestamp": 1720000001000
}
],
"history": {
"current_id": "assistant-msg-id",
"messages": {
"user-msg-id": {
"id": "user-msg-id",
"role": "user",
"content": "Hi, what is the capital of France?",
"timestamp": 1720000000000,
"models": ["gpt-4o"]
},
"assistant-msg-id": {
"id": "assistant-msg-id",
"role": "assistant",
"content": "",
"parentId": "user-msg-id",
"modelName": "gpt-4o",
"modelIdx": 0,
"timestamp": 1720000001000
}
}
}
}
}'
Step 4: Trigger Assistant Completion
Generate the actual AI response using the completion endpoint:
curl -X POST https://<host>/api/chat/completions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "<chatId>",
"id": "assistant-msg-id",
"messages": [
{
"role": "user",
"content": "Hi, what is the capital of France?"
}
],
"model": "gpt-4o",
"stream": true,
"background_tasks": {
"title_generation": true,
"tags_generation": false,
"follow_up_generation": false
},
"features": {
"code_interpreter": false,
"web_search": false,
"image_generation": false,
"memory": false
},
"variables": {
"{{USER_NAME}}": "",
"{{USER_LANGUAGE}}": "en-US",
"{{CURRENT_DATETIME}}": "2025-07-14T12:00:00Z",
"{{CURRENT_TIMEZONE}}": "Europe"
},
"session_id": "session-id"
}'
Step 4.1: Trigger Assistant Completion with Knowledge Integration (RAG)
For advanced use cases involving knowledge bases or document collections, include knowledge files in the completion request:
curl -X POST https://<host>/api/chat/completions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "<chatId>",
"id": "assistant-msg-id",
"messages": [
{
"role": "user",
"content": "Hi, what is the capital of France?"
}
],
"model": "gpt-4o",
"stream": true,
"files": [
{
"id": "knowledge-collection-id",
"type": "collection",
"status": "processed"
}
],
"background_tasks": {
"title_generation": true,
"tags_generation": false,
"follow_up_generation": false
},
"features": {
"code_interpreter": false,
"web_search": false,
"image_generation": false,
"memory": false
},
"variables": {
"{{USER_NAME}}": "",
"{{USER_LANGUAGE}}": "en-US",
"{{CURRENT_DATETIME}}": "2025-07-14T12:00:00Z",
"{{CURRENT_TIMEZONE}}": "Europe"
},
"session_id": "session-id"
}'
Step 5: Wait for Assistant Response Completion
Assistant responses can be handled in two ways depending on your implementation needs:
Option A: Stream Processing (Recommended)
If using stream: true in the completion request, you can process the streamed response in real-time and wait for the stream to complete. This is the approach used by the OpenWebUI web interface and provides immediate feedback.
Option B: Polling Approach
For implementations that cannot handle streaming, poll the chat endpoint until the response is ready. Use a retry mechanism with exponential backoff:
// Example implementation in Java
@Retryable(
retryFor = AssistantResponseNotReadyException.class,
maxAttemptsExpression = "#{${webopenui.retries:50}}",
backoff = @Backoff(delayExpression = "#{${webopenui.backoffmilliseconds:2000}}")
)
public String getAssistantResponseWhenReady(String chatId, ChatCompletedRequest chatCompletedRequest) {
OWUIChatResponse response = owuiService.fetchFinalChatResponse(chatId);
Optional<OWUIMessage> assistantMsg = extractAssistantResponse(response);
if (assistantMsg.isPresent() && !assistantMsg.get().getContent().isBlank()) {
owuiService.completeAssistantMessage(chatCompletedRequest);
return assistantMsg.get().getContent();
}
throw new AssistantResponseNotReadyException("Assistant response not ready yet for chatId: " + chatId);
}
For manual polling, you can use:
# Poll every few seconds until assistant content is populated
while true; do
response=$(curl -s -X GET https://<host>/api/v1/chats/<chatId> \
-H "Authorization: Bearer <token>")
# Check if assistant message has content (response is ready)
if echo "$response" | jq '.chat.messages[] | select(.role=="assistant" and .id=="assistant-msg-id") | .content' | grep -v '""' > /dev/null; then
echo "Assistant response is ready!"
break
fi
echo "Waiting for assistant response..."
sleep 2
done
Step 6: Complete Assistant Message
Once the assistant response is ready, mark it as completed:
curl -X POST https://<host>/api/chat/completed \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "<chatId>",
"id": "assistant-msg-id",
"session_id": "session-id",
"model": "gpt-4o"
}'
Step 7: Fetch Final Chat
Retrieve the completed conversation:
curl -X GET https://<host>/api/v1/chats/<chatId> \
-H "Authorization: Bearer <token>"
Additional API Endpoints
Fetch Knowledge Collection
Retrieve knowledge base information for RAG integration:
curl -X GET https://<host>/api/v1/knowledge/<knowledge-id> \
-H "Authorization: Bearer <token>"
Fetch Model Information
Get details about a specific model:
curl -X GET https://<host>/api/v1/models/model?id=<model-name> \
-H "Authorization: Bearer <token>"
Send Additional Messages to Chat
For multi-turn conversations, you can send additional messages to an existing chat:
curl -X POST https://<host>/api/v1/chats/<chatId> \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"chat": {
"id": "<chatId>",
"messages": [
{
"id": "new-user-msg-id",
"role": "user",
"content": "Can you tell me more about this?",
"timestamp": 1720000002000,
"models": ["gpt-4o"]
}
],
"history": {
"current_id": "new-user-msg-id",
"messages": {
"new-user-msg-id": {
"id": "new-user-msg-id",
"role": "user",
"content": "Can you tell me more about this?",
"timestamp": 1720000002000,
"models": ["gpt-4o"]
}
}
}
}
}'
Response Processing
Parsing Assistant Responses
Assistant responses may be wrapped in markdown code blocks. Here's how to clean them:
# Example raw response from assistant
raw_response='```json
{
"result": "The capital of France is Paris.",
"confidence": 0.99
}
```'
# Clean the response (remove markdown wrappers)
cleaned_response=$(echo "$raw_response" | sed 's/^```json//' | sed 's/```$//' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
echo "$cleaned_response" | jq '.'
This cleaning process handles:
- Removal of ````json` prefix
- Removal of ```` suffix
- Trimming whitespace
- JSON validation