Skip to content
Hoody.com

Create and manage file system watchers with real-time event streaming. The Watch service runs as a sidecar inside each container and exposes its API on the watch-1 subdomain. Use it to monitor one or more filesystem paths, retrieve a replay buffer of past events, and stream live file changes to long-lived SSE or WebSocket clients.

Check whether the Watch service is up. Returns service status, PID, IP, build, and runtime metadata.

This endpoint takes no parameters.

Terminal window
curl -X GET 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/health' \
-H 'Authorization: Bearer <token>'

Service is healthy.

{
"status": "ok",
"service": "watch",
"started": "2026-02-11T15:30:00Z",
"pid": 1234,
"ip": "10.0.0.5",
"built": "2026-02-10",
"userAgent": "hoody-watch/1.0",
"memory": {
"rss": 33554432,
"heap": 16777216
},
"fds": 12
}

Create a new watcher bound to one or more paths. The watcher begins receiving file events immediately and retains them in a replay buffer sized by history_size.

NameTypeRequiredDescription
pathsarray of stringYesAbsolute or relative filesystem paths to watch.
recursivebooleanNoRecursive mode. Defaults to server config value.
includearray of stringNoOptional include glob patterns. If present, path must match one include.
excludearray of stringNoOptional exclude glob patterns. Excludes take precedence over includes.
ignore_dirsarray of stringNoDirectory names or subdirectory paths to skip entirely. Defaults to the server-configured ignore list (node_modules, .git, target, and similar). Pass [] to disable.
skip_hiddenbooleanNoWhen true, hidden directories (dot-prefixed basenames) are never watched or scanned into. Hidden files in visible directories still emit events.
kindsarray of stringNoEvent kinds to report: created, modified, removed, renamed, metadata, overflow, other.
coalesce_msintegerNoCoalescing window in milliseconds.
history_sizeintegerNoReplay history capacity for this watcher.
NameInTypeRequiredDescription
Idempotency-KeyheaderstringNoMakes the create safe to retry. A create with a key used in the last 10 minutes with the same body returns the watcher that first create made (200) instead of a second watcher; with another body it is refused (409 IDEMPOTENCY_KEY_REUSED). Use a new key for each watcher, such as a random UUID. At most 256 characters.
Terminal window
curl -X POST 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"paths": ["/app/src"],
"recursive": true,
"kinds": ["created", "modified", "removed"],
"coalesce_ms": 100,
"history_size": 1024
}'

The Idempotency-Key was already used, with the same body, by a create in the last 10 minutes: nothing is created and the watcher that create made is returned (as it was created, if it has been deleted since).

{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"created_at": "2026-02-11T15:30:00Z",
"config": {
"paths": ["/app/src"],
"recursive": true,
"include": [],
"exclude": [],
"ignore_dirs": ["node_modules", ".git"],
"skip_hidden": false,
"kinds": ["created", "modified", "removed"],
"coalesce_ms": 100,
"history_size": 1024,
"history_limit_bytes": 1048576
},
"stats": {
"events_seen": 0,
"events_broadcast": 0,
"events_dropped": 0,
"stream_errors": 0,
"active_clients": 0
}
}

List watchers with pagination.

NameInTypeRequiredDescription
pagequeryintegerNoPage number (1-based).
limitqueryintegerNoItems per page (1-200).
Terminal window
curl -X GET 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers?page=1&limit=20' \
-H 'Authorization: Bearer <token>'

Watcher list.

{
"items": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"created_at": "2026-02-11T15:30:00Z",
"config": {
"paths": ["/app/src"],
"recursive": true,
"include": [],
"exclude": [],
"ignore_dirs": ["node_modules", ".git"],
"skip_hidden": false,
"kinds": ["created", "modified", "removed"],
"coalesce_ms": 100,
"history_size": 1024,
"history_limit_bytes": 1048576
},
"stats": {
"events_seen": 1542,
"events_broadcast": 1530,
"events_dropped": 12,
"stream_errors": 0,
"active_clients": 1
}
}
],
"page": 1,
"limit": 20,
"total": 1
}

Get a single watcher by id, including its resolved configuration and live stats.

NameInTypeRequiredDescription
idpathstringYesWatcher id.
Terminal window
curl -X GET 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers/f47ac10b-58cc-4372-a567-0e02b2c3d479' \
-H 'Authorization: Bearer <token>'

Watcher details.

{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"created_at": "2026-02-11T15:30:00Z",
"config": {
"paths": ["/app/src"],
"recursive": true,
"include": [],
"exclude": [],
"ignore_dirs": ["node_modules", ".git"],
"skip_hidden": false,
"kinds": ["created", "modified", "removed"],
"coalesce_ms": 100,
"history_size": 1024,
"history_limit_bytes": 1048576
},
"stats": {
"events_seen": 1542,
"events_broadcast": 1530,
"events_dropped": 12,
"stream_errors": 0,
"active_clients": 1
}
}

Reconfigure a live watcher in place. Omitted fields keep their current values. The watcher keeps its id, replay history, and connected SSE/WebSocket clients; only the file-system backend is replaced.

The new backend starts before the old one stops, and events the old backend had already queued are processed before the handoff completes. A change under a path watched by both configurations is not lost across the swap, and a change under a path only the old configuration watched is delivered if the old backend saw it before stopping.

The drain is bounded: if the old backend has not finished within 5 seconds, the handoff completes anyway and events still queued in the old backend at that point are dropped, with a warning in the service log. A request that fails leaves the watcher unchanged. A body with no field is refused with 400 INVALID_REQUEST. A body whose fields all equal the current values returns the watcher as it is, without replacing the backend.

NameInTypeRequiredDescription
idpathstringYesWatcher id.
NameTypeRequiredDescription
pathsarray of stringNoReplacement path list (non-empty). Omit to keep the current paths.
recursivebooleanNoRecursive mode.
includearray of stringNoReplacement include globs. [] removes every include filter.
excludearray of stringNoReplacement exclude globs. [] removes every exclude filter.
ignore_dirsarray of stringNoReplacement ignore list. [] disables it; omit to keep the current list.
skip_hiddenbooleanNoSkip hidden directories.
kindsarray of stringNoReplacement event kind filter. [] accepts every kind.
coalesce_msintegerNoCoalescing window in milliseconds (minimum 10).
history_sizeintegerNoReplay history capacity (minimum 32). Growing keeps every retained event; shrinking drops only the oldest events beyond the new cap.
Terminal window
curl -X PATCH 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers/f47ac10b-58cc-4372-a567-0e02b2c3d479' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"coalesce_ms": 250,
"kinds": ["created", "modified", "removed", "renamed"]
}'

Watcher reconfigured.

{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"created_at": "2026-02-11T15:30:00Z",
"config": {
"paths": ["/app/src"],
"recursive": true,
"include": [],
"exclude": [],
"ignore_dirs": ["node_modules", ".git"],
"skip_hidden": false,
"kinds": ["created", "modified", "removed", "renamed"],
"coalesce_ms": 250,
"history_size": 1024,
"history_limit_bytes": 1048576
},
"stats": {
"events_seen": 1542,
"events_broadcast": 1530,
"events_dropped": 12,
"stream_errors": 0,
"active_clients": 1
}
}

Delete a watcher and stop watching its paths.

NameInTypeRequiredDescription
idpathstringYesWatcher id.
Terminal window
curl -X DELETE 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers/f47ac10b-58cc-4372-a567-0e02b2c3d479' \
-H 'Authorization: Bearer <token>'

Watcher deleted.

{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"deleted": true
}

List events from the watcher’s replay buffer. Use since_id (an integer event id) or since_timestamp to bound by a cursor, after_id to walk a long history safely, or page and limit for simple offset paging.

NameInTypeRequiredDescription
idpathstringYesWatcher id.
since_idqueryintegerNoReplay events strictly after this event id.
since_timestampquerystringNoReplay events strictly after this timestamp. Accepts RFC3339 (for example 2026-02-11T15:30:00Z), Unix seconds, or Unix milliseconds.
pagequeryintegerNoPage number (1-based), counted from the oldest event still retained. Ignored when after_id is set.
limitqueryintegerNoItems per page (1-200).
after_idqueryintegerNoContinue a walk: return the limit events after this event id (the previous response’s next_after_id). since_id, since_timestamp, and page are ignored when it is set.
Terminal window
curl -X GET 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers/f47ac10b-58cc-4372-a567-0e02b2c3d479/events?since_id=100&limit=50' \
-H 'Authorization: Bearer <token>'

Watcher event history.

{
"items": [
{
"id": 101,
"watcher_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"kind": "modified",
"path": "/app/src/index.ts",
"timestamp": "2026-02-11T15:30:00.123Z",
"is_dir": false,
"new_size_bytes": 1024,
"old_size_bytes": 980,
"details": null,
"old_path": null
}
],
"page": 1,
"limit": 50,
"total": 1,
"has_more": false,
"oldest_available_id": 1,
"oldest_available_timestamp": "2026-02-11T15:00:00Z",
"newest_available_id": 101,
"newest_available_timestamp": "2026-02-11T15:30:00.123Z",
"next_after_id": null
}

GET /api/v1/watch/watchers/{id}/events/sse

Section titled “GET /api/v1/watch/watchers/{id}/events/sse”

Stream events for a watcher over Server-Sent Events. Each file event is one event: file_event frame whose id: is the event id and whose data: is a FileEvent JSON object. A event: lag frame (data {"type":"lag",…}) reports an unrecoverable gap and ends the stream. Keep-alive comments arrive every 10 seconds. Resume after a drop with since_id set to the last id: received.

NameInTypeRequiredDescription
idpathstringYesWatcher id.
since_idqueryintegerNoReplay events strictly after this event id.
since_timestampquerystringNoReplay events strictly after this timestamp. Accepts RFC3339 (for example 2026-02-11T15:30:00Z), Unix seconds, or Unix milliseconds.
Terminal window
curl -N -X GET 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers/f47ac10b-58cc-4372-a567-0e02b2c3d479/events/sse?since_id=100' \
-H 'Authorization: Bearer <token>'

SSE stream established. Each file event is one event: file_event frame whose id: is the event id and whose data: is a FileEvent JSON object. A event: lag frame (data {"type":"lag",…}) reports an unrecoverable gap and ends the stream. Keep-alive comments arrive every 10 seconds. Resume after a drop with since_id set to the last id: received.

event: file_event
id: 42
data: {"id":42,"watcher_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","kind":"modified","path":"/app/src/index.ts","timestamp":"2026-02-11T15:30:00.123Z","new_size_bytes":1024}
event: lag
data: {"type":"lag","details":"events dropped, history rescanned"}

Stream events for a watcher over WebSocket. The server sends one text frame per file event: a FileEvent JSON object plus "type":"file_event". A {"type":"lag",…} frame reports an unrecoverable gap and is followed by close. Client text/binary frames are ignored. Resume after a drop with since_id set to the last event id received.

The WebSocket URL is wss://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers/{id}/events/ws and accepts the same since_id and since_timestamp query parameters as the SSE endpoint.

NameInTypeRequiredDescription
idpathstringYesWatcher id.
since_idqueryintegerNoReplay events strictly after this event id.
since_timestampquerystringNoReplay events strictly after this timestamp. Accepts RFC3339 (for example 2026-02-11T15:30:00Z), Unix seconds, or Unix milliseconds.
Terminal window
curl -i -N \
-H 'Connection: Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers/f47ac10b-58cc-4372-a567-0e02b2c3d479/events/ws?since_id=100'

The connection upgrades to a WebSocket. After the upgrade, the server sends one text frame per file event: a FileEvent JSON object plus "type":"file_event". A {"type":"lag",…} frame reports an unrecoverable gap and is followed by close. Client text/binary frames are ignored. Resume after a drop with since_id set to the last event id received. This response carries no body.