Skip to Content
API📡 Realtime Events

Realtime Events & Notifications

DocsGPT pushes realtime updates to the browser over Server-Sent Events (SSE). This is what powers the upload toasts, tool-approval prompts, and other live notifications in the UI. There are two channels:

  • User events — GET /api/events: a per-user notification stream (ingestion progress, tool approvals, MCP OAuth completion, …).
  • Chat reconnect — GET /api/messages/<message_id>/events: resume an answer stream that was interrupted mid-generation.
ℹ️

Both channels require Redis (already a DocsGPT dependency). The publisher can be turned off instance-wide with ENABLE_SSE_PUSH=false.

⚠️

Adding an MCP server that signs in with OAuth needs ENABLE_SSE_PUSH=true (the default): the sign-in link and its result reach the web app only as mcp.oauth.* events. With the publisher off, the sign-in never starts in the browser.

User events channel

Open an SSE connection to receive notifications for the authenticated user:

GET /api/events Accept: text/event-stream Authorization: Bearer <token>

Authenticate with the session token the web app uses; with AUTH_TYPE unset no token is needed. Personal access tokens are refused on this route. Each record is one id: line and one data: line holding a JSON envelope:

id: 1718900000000-0 data: {"type":"source.ingest.completed","ts":"2026-06-20T16:13:20.000Z","user_id":"alice","topic":"user:alice","scope":{"kind":"source","id":"<source_id>"},"payload":{"source_id":"<source_id>","filename":"guide.pdf","tokens":48213,"operation":"upload","limited":false},"id":"1718900000000-0"}
FieldMeaning
typeThe event type (table below).
tsWhen the event was published (UTC, ISO 8601).
user_id, topicThe user the event is for, and its internal channel name.
scopeWhat the event is about: kind (source, attachment, conversation, schedule, connection, mcp_oauth, team, resource) and id. Match on it to tell events for different items apart.
payloadEvent-specific fields.
idThe same id as the id: line; your cursor for reconnecting.

Between events the stream sends keepalive comments (lines starting with :); SSE clients ignore them.

Event types:

TypeScopeSent whenMain payload fields
source.ingest.queuedsourceAn upload, remote ingest or re-ingest was queued.source_id, filename or job_name, operation
source.ingest.progresssourceParsing or embedding progressed.current, total, stage (parsing or embedding)
source.ingest.completedsourceThe source is ready.source_id, tokens, operation, and filename (uploads and re-ingests) or job_name and loader (remote and connector sources)
source.ingest.failedsourceIngestion failed.source_id, operation, error
graph.extract.progress, graph.extract.completed, graph.extract.failedsourceGraphRAG extraction progressed, finished or failed.source_id; progress: current, total, nodes, edges; completed: nodes, edges, chunks_processed, skipped_over_cap, failed_chunks; failed: error
attachment.queued, attachment.progress, attachment.completed, attachment.failedattachmentA chat attachment is being processed.attachment_id, filename
tool.approval.requiredconversationA turn paused on tools that need approval.conversation_id, message_id, pending_tool_calls
tool.approval.clearedconversationA pending approval is gone: it expired, or the paused turn failed.conversation_id, reason (expired or failed)
connection.reconnect_neededconnectionA connected account needs signing in again.connection_id, connector_key, name, source_count, tool_count
mcp.oauth.in_progress, mcp.oauth.awaiting_redirect, mcp.oauth.completed, mcp.oauth.failedmcp_oauth (id is the OAuth task id)An MCP server’s OAuth sign-in progressed.task_id on every event; message on in_progress and awaiting_redirect; the authorization_url to open on awaiting_redirect; tools and tools_count on completed; error on failed
schedule.run.completed, schedule.run.failedscheduleA scheduled run finished.run_id, schedule_id, agent_id, status, and error_type/error on failure
schedule.autopausedscheduleA schedule paused itself after repeated failures.run_id, schedule_id, consecutive_failure_count
schedule.completed, schedule.resumed, schedule.cancelledscheduleA schedule’s status changed.schedule_id, status
schedule.message.appendedconversationA scheduled run added a message to a conversation.conversation_id, message_id, schedule_id, run_id
team.member_addedteamYou were added to a team.team_id, team_name, role, added_by
resource.sharedresourceSomething was shared with you through a team.resource_type, resource_id, resource_name, access_level, team_id, team_name, shared_by
backlog.truncatedYour Last-Event-ID is older than what a replay can return (past EVENTS_STREAM_MAXLEN or EVENTS_REPLAY_MAX_AGE_HOURS) or is malformed. Clear your cursor and refetch state.oldest_retained_id (empty for a malformed cursor)

New event types can be added, so ignore types you don’t handle.

Reconnecting and backlog replay

Events are journaled per user in a Redis Stream so a client that reconnects can catch up on what it missed. Send the last id you processed and DocsGPT replays everything after it:

GET /api/events Last-Event-ID: 1718900000000-0

(You may also pass it as a last_event_id query parameter.) Each delivered event carries its own id:, so your cursor advances as you read. If you fall a long way behind, the snapshot is delivered across several reconnects rather than all at once.

A few bounded behaviors to be aware of:

  • The backlog is capped at EVENTS_STREAM_MAXLEN entries (default 1000), and a replay returns only events from the last EVENTS_REPLAY_MAX_AGE_HOURS (default 48). If your Last-Event-ID is older than either limit, or isn’t a valid event id, you receive a backlog.truncated event — reset your cursor and refetch current state.
  • Each snapshot is capped at EVENTS_REPLAY_MAX_PER_REQUEST entries per request (default 200); reconnect to continue.
  • There is a per-user cap on simultaneous connections (SSE_MAX_CONCURRENT_PER_USER, default 8) and a windowed replay budget. Exceeding either returns HTTP 429 — back off and retry.

Chat answer reconnect

When an answer is streaming and the connection drops, resume it without losing the in-progress generation:

GET /api/messages/<message_id>/events

Send the sequence number of the last event you processed (the id: line of each /stream record) as the Last-Event-ID header or the last_event_id query parameter; without one, the replay starts at the beginning of the message. The route replays the message’s events past that number and tails the rest live, with keepalive comments in between. It is backed by the Postgres message_events journal (retained for MESSAGE_EVENTS_RETENTION_DAYS, default 14).

It takes a session token, or a personal access token with conversations:read or chat:run and no resource restrictions. Only the owner of the message can read it (others get 404), and the connection counts toward the same per-user cap: over SSE_MAX_CONCURRENT_PER_USER it returns HTTP 429.

⚠️

Both channels need the API served through the ASGI app (docsgpt.asgi:asgi_app), as docsgpt api, docsgpt dev and the Docker images do. Under flask run, GET /api/events and GET /api/messages/<message_id>/events return 404. See ASGI-only features.

Settings

SettingDefaultPurpose
ENABLE_SSE_PUSHtrueMaster switch for the publisher and channel.
EVENTS_STREAM_MAXLEN1000Per-user backlog cap (approximate).
SSE_KEEPALIVE_SECONDS15Keepalive comment-frame cadence (keep below your proxy’s idle timeout).
SSE_MAX_CONCURRENT_PER_USER8Max simultaneous SSE connections per user (0 disables the cap).
ASYNC_REDIS_MAX_CONNECTIONS2000Redis connection pool per API worker. Each open SSE connection holds one, so this caps concurrent streams per worker.
EVENTS_REPLAY_MAX_PER_REQUEST200Max backlog entries per replay request.
EVENTS_REPLAY_MAX_AGE_HOURS48Oldest backlog entry a replay returns.
EVENTS_REPLAY_BUDGET_REQUESTS_PER_WINDOW30Per-user replay requests per window (0 disables).
EVENTS_REPLAY_BUDGET_WINDOW_SECONDS60Replay budget window length.
MESSAGE_EVENTS_RETENTION_DAYS14Retention for the chat-stream message_events journal.
ℹ️

Operators debugging delivery issues (“the toast never appeared”, “the answer didn’t reconnect”) can follow the SSE notifications runbook.

Last updated on