Skip to main content
POST /api/chat/send accepts one student message and responds with text/event-stream. OpenAPI cannot describe the event framing, so this page is the contract.

Request

Send message, mediaIds, image, or a combination. A request with none of them returns 400 with {"error":"empty message"}. The 400 arrives before the stream, as normal JSON.
Attachments go through POST /api/media first. Read Media. The image field still works and puts the bytes inline as base64, but it costs about 33 percent more bytes and holds the request open while the server stores them.

Lala answers in one message or several

Lala writes the way a person texts. Most turns are one message. Some are two or three short ones. Each message becomes its own row with its own id, and the stream announces each one. The index field is the position of the message in the turn. It starts at 0 for every turn.

Response events

The response status is 200 and the content type is text/event-stream. Each event has a name and a JSON data payload.
ChatMessageStartEvent
Lala started a new message. Open a new bubble.
ChatChunkEvent
One text delta of the message at index. Repeats many times.
ChatMessageEndEvent
The message at index is complete. It has no id yet.
ChatRestartEvent
Rare. The turn starts again. Discard every message from this turn. The next index is 0.
ChatToolEvent
Lala started or finished a tool. Cosmetic. Use it to say what the wait is for.
ChatSyncEvent
A tool wrote data that the application shows. Reload those screens.
ChatDoneEvent
Terminal. Holds the persisted messages, with their ids, the reactions, and every resource that the turn wrote.
ChatErrorEvent
Terminal. The turn failed.
The stream emits exactly one terminal event: done or error. A model failure is an error event, not an HTTP status. The HTTP status stays 200 in both cases.

Rules for the client

  1. On message_start, open a new bubble for that index.
  2. Append each chunk payload to the bubble named by its index.
  3. On message_end, mark that bubble complete.
  4. On restart, delete every bubble of this turn and wait for the next message_start.
  5. On done, replace your bubbles with the messages array. Each entry carries the real id.
  6. On done, show the emoji in reactions. The array is usually empty. Lala reacts rarely.
  7. On error, show a retry control. Do not show the partial text as a finished answer.
  8. If the socket closes with no terminal event, treat the turn as failed.
  9. On sync, reload the named screens. Do not wait for done.
  10. On tool, restart, done, or error, update the activity indicator. A start with no end follows a failed turn, so a terminal event must clear the indicator.

Tools change data while Lala writes

A turn can write to the planner, the deneme history, and the student profile. Lala does this with tools, in the middle of the answer. The sync event names the data that changed: Each resource fires one sync event per turn, at the moment of the write. The write is complete when the event arrives, so a read after it returns the new data. The changed array on done repeats the full set. A client that missed an event still reloads the correct screens. The tool event carries the name of the tool. The application maps the name to its own copy, for example plan_study to “programa yazıyor”. A turn can call several tools, one after the other.
The server persists the student message before the model runs. It persists the answer after the model runs. A turn that fails mid-stream can leave the student message in the history with no answer. Read GET /api/chat/messages to resynchronize after an error.

Reactions

Lala’s reaction lands on the student message it answers, with actor set to lala. The student reacts through POST /api/chat/messages/{id}/reactions.
To remove one, call DELETE /api/chat/messages/{id}/reactions/{emoji} with the emoji percent-encoded. Both calls return the whole message with its current reactions. Both are idempotent.

No resume

You cannot reconnect to a running turn. If the application goes to the background and the connection drops, the server still completes the turn and writes the messages. The messages then arrive on the realtime stream. Read Realtime updates.

Timing

The server aborts the model call after 60 seconds. One turn can take several model steps, because Lala can call tools. Allow at least 90 seconds of client timeout on this request. Do not set an idle timeout below the gap between two chunks.

Client code

Do not use the browser EventSource API. EventSource sends only GET requests and cannot set the Authorization header. Use fetch with a stream reader, as shown above.

Concurrency

Send one turn at a time for one student. The server does not reject a second concurrent request, but both turns read and write the same conversation history. Disable the send control until a terminal event arrives.