Notifications API
Notifications track system events, task completions, errors, and approval requests. The pending-approvals stream provides real-time SSE updates so the UI can surface human-in-the-loop decisions without polling.
Quick Start
List pending notifications, approve a tool request, and stream approvals in real time:
// 1. Check for unread notifications
const res1: Response = await fetch('/api/notifications?unread=true');
const notifications: Array<{ id: string; type: string; title: string }> = await res1.json();
console.log(`${notifications.length} unread notifications`);
// 2. List pending approval requests (agents waiting for permission)
const res2: Response = await fetch('/api/notifications/pending-approvals');
const approvals: Array<{ id: string; taskId: string; toolName: string }> = await res2.json();
if (approvals.length > 0) {
console.log(`${approvals.length} approvals waiting`);
// 3. Approve the first pending request (via Tasks API respond endpoint)
const approval = approvals[0];
await fetch(`/api/tasks/${approval.taskId}/respond`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
notificationId: approval.id,
behavior: 'allow',
}),
});
}
// 4. Stream approvals in real time so the UI can react instantly
const source: EventSource = new EventSource('/api/notifications/pending-approvals/stream');
source.onmessage = (event: MessageEvent) => {
const pending: Array<{ id: string; taskId: string }> = JSON.parse(event.data);
console.log(`${pending.length} approval(s) pending`);
};
// 5. Mark everything as read when done
await fetch('/api/notifications/mark-all-read', { method: 'PATCH' }); Base URL
/api/notifications
Endpoints
List Notifications
/api/notifications Retrieve notifications with optional filtering. Returns up to 100 results, newest first.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| unread | string | — | Filter to unread only when set to "true" |
| type | string | — | Filter by notification type |
| countOnly | string | — | When "true", return only the unread count |
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Notification identifier |
| type | enum | * | Notification type (task_complete, error, approval, etc.) |
| title | string | * | Notification title |
| message | string | — | Notification body text |
| read | boolean | * | Whether the notification has been read |
| createdAt | ISO 8601 | * | Creation timestamp |
Response Body (countOnly)
| Field | Type | Req | Description |
|---|---|---|---|
| count | number | * | Count of unread notifications |
Fetch unread notifications to power a notification badge or dropdown in the UI:
// Fetch unread notifications for the UI badge
const res: Response = await fetch('http://localhost:3000/api/notifications?unread=true');
const notifications: Array<{ type: string; title: string; message: string }> = await res.json();
notifications.forEach((n) => {
const icon: string = n.type === 'error' ? '!' : n.type === 'approval' ? '?' : 'i';
console.log(`[${icon}] ${n.title} — ${n.message}`);
});
// Or just get the count for a badge
const countRes: Response = await fetch('http://localhost:3000/api/notifications?countOnly=true');
const { count }: { count: number } = await countRes.json();
console.log(`${count} unread`); Example response:
[
{
"id": "notif-8a3b-2c1d",
"type": "task_complete",
"title": "Task completed",
"message": "Analyze Q4 revenue trends finished successfully",
"read": false,
"createdAt": "2026-04-03T10:32:44.000Z"
},
{
"id": "notif-7b4c-3d2e",
"type": "approval",
"title": "Permission required",
"message": "Agent wants to write to /reports/q4-analysis.md",
"read": false,
"createdAt": "2026-04-03T10:31:20.000Z"
}
] Mark Notification Read
/api/notifications/{id} Update the read status of a single notification.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| read | boolean | — | Read status (defaults to true) |
Response 200 — Updated notification object
Mark a notification as read after the user has seen it:
// Mark as read when the user clicks on it
await fetch('http://localhost:3000/api/notifications/notif-8a3b-2c1d', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ read: true }),
}); Errors: 404 — Notification not found
Delete Notification
/api/notifications/{id} Permanently delete a single notification.
Response 200 — { "success": true }
// Permanently delete a notification
await fetch('http://localhost:3000/api/notifications/notif-8a3b-2c1d', { method: 'DELETE' }); Mark All Read
/api/notifications/mark-all-read Mark all unread notifications as read in a single operation.
Response 200 — { "success": true }
Clear the notification badge by marking everything as read:
// Clear the notification badge
await fetch('http://localhost:3000/api/notifications/mark-all-read', { method: 'PATCH' }); Pending Approvals
List Pending Approvals
/api/notifications/pending-approvals List all pending approval payloads that require human-in-the-loop decisions.
Response 200 — Array of approval payload objects
Fetch pending approvals to show a queue of agent requests waiting for human decisions:
// List approval requests waiting for human decision
const res: Response = await fetch('http://localhost:3000/api/notifications/pending-approvals');
const approvals: Array<{ taskId: string; toolName: string; message: string }> = await res.json();
approvals.forEach((a) => {
console.log(`Task ${a.taskId}: ${a.toolName} — ${a.message}`);
}); Example response:
[
{
"id": "notif-7b4c-3d2e",
"taskId": "task-9d4e-a1b2",
"toolName": "Write",
"toolInput": { "file_path": "/reports/q4-analysis.md" },
"message": "Agent wants to write to /reports/q4-analysis.md",
"createdAt": "2026-04-03T10:31:20.000Z"
}
] Stream Pending Approvals (SSE)
/api/notifications/pending-approvals/stream Server-Sent Events stream that pushes real-time updates whenever the pending approvals list changes. Polls the database every 750ms and only emits when the set changes. Sends keepalive comments every 15 seconds.
Event format: data: [<approval>, ...]
Keepalive: : keepalive
Connect to the SSE stream to get instant updates when agents request permissions — no polling needed:
// Real-time approval stream — UI can react instantly
const source: EventSource = new EventSource('http://localhost:3000/api/notifications/pending-approvals/stream');
source.onmessage = (event: MessageEvent) => {
const approvals: Array<{ taskId: string; toolName: string; message: string }> = JSON.parse(event.data);
if (approvals.length === 0) {
console.log('No pending approvals');
return;
}
// Show each pending approval in the UI
approvals.forEach((a) => {
console.log(`[${a.taskId}] ${a.toolName}: ${a.message}`);
});
};
source.onerror = () => {
console.error('SSE connection lost — will auto-reconnect');
}; Example stream events:
data: [{"id":"notif-7b4c-3d2e","taskId":"task-9d4e-a1b2","toolName":"Write","message":"Agent wants to write to /reports/q4-analysis.md"}]
: keepalive
data: [] Notification Types
| Type | Description |
|---|---|
| task_complete | A task finished execution successfully. |
| error | An error occurred during task or workflow execution. |
| approval | A tool invocation requires human approval before proceeding. |
| system | System-level events (budget warnings, runtime changes). |