Skip to main content

SSE Event Stream

The SSE (Server-Sent Events) stream delivers real-time tenant events to connected clients without polling. Events are pushed as they happen — record CRUD, rule executions, and agent messages.

Endpoint

GET /api/v1/tenants/:slug/events/stream

Authentication

Pass a tenant API key (fyso_ak_* or fyso_pkey_*) in the Authorization header:

curl -N "https://api.fyso.dev/api/v1/tenants/my-workspace/events/stream" \
-H "Authorization: Bearer fyso_ak_..."

The key must belong to the specified tenant. Admin session tokens are not accepted on this endpoint.

Connection limits

LimitValue
Max connections per tenant500 (env: SSE_MAX_CONNECTIONS_PER_TENANT)
Max connections globally1000 (env: SSE_MAX_CONNECTIONS_GLOBAL)
Backpressure threshold100 queued events
Idle timeout10 minutes
Heartbeat interval5 seconds (env: SSE_HEARTBEAT_INTERVAL_MS)

If the per-tenant or global limit is reached, the endpoint returns 429.

Slow consumers that fall more than 100 events behind receive a backpressure error event and the connection is closed.

Heartbeat

The server sends a heartbeat event every 5 seconds. An immediate heartbeat is sent right after the connected event — this eliminates the idle gap that previously caused some proxies to close the connection before the first real event arrived.

The idle safety timeout (10 minutes) resets on every heartbeat and every business event, so in practice it only triggers when the client has vanished without closing the TCP connection.

Query parameters

ParameterDescription
entitiesComma-separated entity names to filter record events. Omit to receive all entities. Example: ?entities=invoices,clients
eventsComma-separated event types to receive. Omit to receive all event types. Example: ?events=record.created,rule.executed
agent_idAgent ID from .fyso-agent. When set, also delivers message.received events addressed to this agent.

You can combine filters:

curl -N "https://api.fyso.dev/api/v1/tenants/my-workspace/events/stream?entities=orders&events=record.created,rule.executed" \
-H "Authorization: Bearer fyso_ak_..."

Event types

TypeTrigger
record.createdA record was created in any entity
record.updatedA record was updated
record.deletedA record was deleted
rule.executedA business rule ran and completed successfully
rule.failedA business rule ran and threw an error
message.receivedAn agent message was delivered (requires ?agent_id=)
message.chain_limitAn auto-run chain was halted at depth 5
connectedSent immediately on connect (prevents proxy timeout)
errorStream-level error (IDLE_TIMEOUT, BACKPRESSURE)

Event shapes

Record events

event: record.created
id: 42
data: {
"id": "evt_uuid",
"type": "record.created",
"timestamp": "2026-03-23T10:00:00Z",
"tenant": "my-workspace",
"entity": "orders",
"record_id": "uuid",
"data": {
"fields": { "status": "pending", "total": 150.00 },
"triggered_by": "api"
}
}

The triggered_by field identifies what initiated the change:

ValueMeaning
mcpMCP tool call
apiDirect REST API call
flowAutomation flow
webhookIncoming webhook
uiWeb admin panel
ruleRules engine (e.g. update_related)
systemInternal (migrations, seeding)

Rule events

event: rule.executed
id: 43
data: {
"id": "evt_uuid",
"type": "rule.executed",
"timestamp": "2026-03-23T10:00:00Z",
"tenant": "my-workspace",
"data": {
"rule_name": "notify-on-payment",
"trigger_type": "after_save",
"entity": "orders",
"record_id": "uuid",
"result": "completed"
}
}

rule.failed uses the same shape but includes error instead of result:

{
"data": {
"rule_name": "notify-on-payment",
"trigger_type": "after_save",
"entity": "orders",
"record_id": "uuid",
"error": "Integration timeout after 10s"
}
}

Message events

Only delivered when ?agent_id=<id> is set:

event: message.received
id: 44
data: {
"id": "msg_uuid",
"type": "message.received",
"timestamp": "2026-03-23T10:00:00Z",
"tenant": "my-workspace",
"to_agent": "cero-a3f2c1",
"from_agent": "pluma-b7e2d4",
"message_id": "uuid",
"subject": "PR review complete",
"priority": "normal"
}

The payload of the message is not included in the SSE event. Fetch it with GET /agent-messages/:messageId/read.

Error events

event: error
data: { "type": "error", "code": "IDLE_TIMEOUT", "message": "Connection closed due to inactivity" }
event: error
data: { "type": "error", "code": "BACKPRESSURE", "message": "Connection closed: client is too slow to consume events" }

Reconnection

The SSE id field is set on every event. Standard clients that support Last-Event-ID will send it on reconnect; the server does not replay missed events in the current implementation. If replay is required, re-fetch the relevant records via the REST API after reconnecting.

Example: Node.js client

import EventSource from 'eventsource';

const stream = new EventSource(
'https://api.fyso.dev/api/v1/tenants/my-workspace/events/stream?entities=orders',
{ headers: { Authorization: 'Bearer fyso_ak_...' } }
);

stream.addEventListener('record.created', (e) => {
const event = JSON.parse(e.data);
console.log('New order:', event.record_id, event.data.fields);
});

stream.addEventListener('error', (e) => {
if (e.data) console.error('Stream error:', JSON.parse(e.data));
});

Example: Using the Fyso Claude Code skill

The /fyso:listen skill connects the SSE stream to a Claude Code channel:

/fyso:listen

This establishes a live event bridge for the current tenant. Events appear as channel messages in the Claude Code session.