Events API
Server-Sent Events (SSE) stream for real-time decision, approval, and policy change notifications.
The events API provides a real-time SSE stream of organization-scoped events. The dashboard uses this to update the UI in real time when decisions are made, approvals are resolved, or policies change.
GET /v1/events/stream
Open a persistent SSE connection. The server sends events as they occur and a keepalive ping every 15 seconds.
Headers
| Header | Required | Description |
|---|---|---|
X-Veto-API-Key or Authorization | Yes | API key or Bearer JWT |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
types | string | No | Comma-separated list of event types to subscribe to. Omit to receive all events. |
Connection
curl -N https://api.veto.so/v1/events/stream \
-H "X-Veto-API-Key: veto_abc123..."With type filtering:
curl -N "https://api.veto.so/v1/events/stream?types=decision,approval" \
-H "X-Veto-API-Key: veto_abc123..."Response format
The connection returns standard SSE format. Each event has a named type and a JSON data payload:
:ok
event: decision
data: {"toolName":"transfer_funds","decision":"allow","latencyMs":12,"createdAt":"2025-01-20T14:30:00Z"}
event: decision
data: {"toolName":"send_email","decision":"deny","reason":"Recipient not in allowlist","createdAt":"2025-01-20T14:30:05Z"}
event: approval
data: {"id":"apr_abc123","toolName":"transfer_funds","status":"approved","resolvedBy":"user@company.com","resolvedAt":"2025-01-20T14:31:00Z"}
:pingEvent types
| Type | Description | Data fields |
|---|---|---|
decision | A tool call was validated | toolName, decision, reason?, latencyMs, mode, createdAt |
approval | An approval status changed | id, toolName, status, resolvedBy?, resolvedAt?, createdAt |
policy | A policy was created, updated, or deleted | toolName, action, version?, updatedAt |
tool | A tool was registered or removed | name, action, createdAt |
Keepalive
The server sends a :ping comment line every 15 seconds to keep the connection alive through proxies and load balancers. SSE comment lines (prefixed with :) should be ignored by clients.
Connection lifecycle
- Client opens the SSE connection
- Server sends
:okto confirm the connection - Events are delivered as they occur, scoped to the authenticated organization
- Server sends
:pingevery 15 seconds - If the connection drops, the client should reconnect (the
EventSourceAPI does this automatically)
Errors
| Status | Code | Description |
|---|---|---|
| 401 | unauthorized | No valid authentication provided |
JavaScript example
const eventSource = new EventSource(
'https://api.veto.so/v1/events/stream?types=decision,approval',
{
headers: {
'X-Veto-API-Key': 'veto_abc123...',
},
}
);
eventSource.addEventListener('decision', (event) => {
const data = JSON.parse(event.data);
console.log(`${data.toolName}: ${data.decision}`);
});
eventSource.addEventListener('approval', (event) => {
const data = JSON.parse(event.data);
console.log(`Approval ${data.id}: ${data.status}`);
});
eventSource.onerror = () => {
console.log('Connection lost, reconnecting...');
};Infrastructure notes
Events are delivered via Redis pub/sub in multi-instance deployments. In single-instance setups (including self-hosted), events are delivered directly in-process without Redis.
The SSE endpoint does not buffer missed events. If the client disconnects and reconnects, it will only receive events from the point of reconnection. For historical data, use the Decisions API.