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.