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.
Health
Section titled “Health”GET /api/v1/watch/health
Section titled “GET /api/v1/watch/health”Check whether the Watch service is up. Returns service status, PID, IP, build, and runtime metadata.
This endpoint takes no parameters.
curl -X GET 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/health' \ -H 'Authorization: Bearer <token>'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.watch.kit.getHealth();Response
Section titled “Response”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}Watchers
Section titled “Watchers”POST /api/v1/watch/watchers
Section titled “POST /api/v1/watch/watchers”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.
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
paths | array of string | Yes | Absolute or relative filesystem paths to watch. |
recursive | boolean | No | Recursive mode. Defaults to server config value. |
include | array of string | No | Optional include glob patterns. If present, path must match one include. |
exclude | array of string | No | Optional exclude glob patterns. Excludes take precedence over includes. |
ignore_dirs | array of string | No | Directory names or subdirectory paths to skip entirely. Defaults to the server-configured ignore list (node_modules, .git, target, and similar). Pass [] to disable. |
skip_hidden | boolean | No | When true, hidden directories (dot-prefixed basenames) are never watched or scanned into. Hidden files in visible directories still emit events. |
kinds | array of string | No | Event kinds to report: created, modified, removed, renamed, metadata, overflow, other. |
coalesce_ms | integer | No | Coalescing window in milliseconds. |
history_size | integer | No | Replay history capacity for this watcher. |
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string | No | Makes 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. |
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 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.watch.watchers.create({ paths: ['/app/src'], recursive: true, kinds: ['created', 'modified', 'removed'], coalesce_ms: 100, history_size: 1024});Response
Section titled “Response”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 }}Watcher created.
{ "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 }}Invalid request (INVALID_REQUEST): a field fails validation, or the body is not JSON, is sent without a JSON content type, lacks paths, has an unknown field or has a value of the wrong type.
{ "code": "INVALID_REQUEST", "message": "path list cannot be empty"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_REQUEST | Invalid request | A field fails validation, the body is not JSON, is sent without a JSON content type, lacks paths, or has a value of the wrong type | Check request fields and retry |
INVALID_PAGINATION | Invalid pagination | Page or limit is out of range or not a whole number | Use page >= 1 and limit between 1 and 200 |
INVALID_CURSOR | Invalid cursor | since_id or after_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or since_id and since_timestamp are both given | Pass one cursor, as the last event id received or a timestamp |
A per-watcher or per-path limit was hit.
{ "code": "LIMIT_EXCEEDED", "message": "watcher limit reached"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
LIMIT_EXCEEDED | Resource limit exceeded | Watcher or path limits exceeded | Reduce paths or delete unused watchers |
HISTORY_GAP | Replay history gap | Requested since_id is older than retained replay history | Reconnect without since_id or increase history_memory_limit_bytes |
IDEMPOTENCY_KEY_REUSED | Idempotency-Key reused | A create sent an Idempotency-Key that an earlier create in the last 10 minutes used with another body | Use a new Idempotency-Key for each watcher; resend the identical body to get the watcher the key made |
The backend failed to initialize the watcher.
{ "code": "WATCHER_START_FAILED", "message": "failed to start watcher: ..."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_START_FAILED | Watcher startup failed | Backend failed to initialize file watcher | Check path permissions and kernel watch limits |
GET /api/v1/watch/watchers
Section titled “GET /api/v1/watch/watchers”List watchers with pagination.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | Page number (1-based). |
limit | query | integer | No | Items per page (1-200). |
curl -X GET 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com/api/v1/watch/watchers?page=1&limit=20' \ -H 'Authorization: Bearer <token>'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.watch.watchers.listIterator({ page: 1, limit: 20 })) { console.log(item);}Response
Section titled “Response”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}Invalid pagination (INVALID_PAGINATION): page or limit is out of range or not a whole number; the message names the parameter.
{ "code": "INVALID_PAGINATION", "message": "Limit must be between 1 and 200"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_REQUEST | Invalid request | Request payload is not readable JSON of the expected shape, failed validation, or (on update) gives no field | Check request fields and retry |
INVALID_PAGINATION | Invalid pagination | Page or limit is out of range or not a whole number | Use page >= 1 and limit between 1 and 200 |
INVALID_CURSOR | Invalid cursor | since_id or after_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or since_id and since_timestamp are both given | Pass one cursor, as the last event id received or a timestamp |
GET /api/v1/watch/watchers/{id}
Section titled “GET /api/v1/watch/watchers/{id}”Get a single watcher by id, including its resolved configuration and live stats.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Watcher id. |
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>'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.watch.watchers.get('f47ac10b-58cc-4372-a567-0e02b2c3d479');Response
Section titled “Response”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 }}No watcher with the given id exists.
{ "code": "WATCHER_NOT_FOUND", "message": "Watcher not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_NOT_FOUND | Watcher not found | No watcher exists for provided id | List watchers and use a valid watcher id |
PATCH /api/v1/watch/watchers/{id}
Section titled “PATCH /api/v1/watch/watchers/{id}”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Watcher id. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
paths | array of string | No | Replacement path list (non-empty). Omit to keep the current paths. |
recursive | boolean | No | Recursive mode. |
include | array of string | No | Replacement include globs. [] removes every include filter. |
exclude | array of string | No | Replacement exclude globs. [] removes every exclude filter. |
ignore_dirs | array of string | No | Replacement ignore list. [] disables it; omit to keep the current list. |
skip_hidden | boolean | No | Skip hidden directories. |
kinds | array of string | No | Replacement event kind filter. [] accepts every kind. |
coalesce_ms | integer | No | Coalescing window in milliseconds (minimum 10). |
history_size | integer | No | Replay history capacity (minimum 32). Growing keeps every retained event; shrinking drops only the oldest events beyond the new cap. |
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"] }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.watch.watchers.update('f47ac10b-58cc-4372-a567-0e02b2c3d479', { coalesce_ms: 250, kinds: ['created', 'modified', 'removed', 'renamed']});Response
Section titled “Response”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 }}Invalid request (INVALID_REQUEST): a field fails validation; the body is not JSON, is sent without a JSON content type, or has an unknown field or a value of the wrong type; or it gives no field at all (nothing to update).
{ "code": "INVALID_REQUEST", "message": "path list cannot be empty"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_REQUEST | Invalid request | A field fails validation; the body is not JSON, is sent without a JSON content type, or has an unknown field or a value of the wrong type; or it gives no field at all (nothing to update) | Check request fields and retry |
INVALID_PAGINATION | Invalid pagination | Page or limit is out of range or not a whole number | Use page >= 1 and limit between 1 and 200 |
INVALID_CURSOR | Invalid cursor | since_id or after_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or since_id and since_timestamp are both given | Pass one cursor, as the last event id received or a timestamp |
No watcher with the given id exists.
{ "code": "WATCHER_NOT_FOUND", "message": "Watcher not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_NOT_FOUND | Watcher not found | No watcher exists for provided id | List watchers and use a valid watcher id |
A per-watcher or per-path limit was hit while applying the new configuration.
{ "code": "LIMIT_EXCEEDED", "message": "watcher limit reached"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
LIMIT_EXCEEDED | Resource limit exceeded | Watcher or path limits exceeded | Reduce paths or delete unused watchers |
HISTORY_GAP | Replay history gap | Requested since_id is older than retained replay history | Reconnect without since_id or increase history_memory_limit_bytes |
The new configuration could not be started; the watcher is unchanged.
{ "code": "WATCHER_START_FAILED", "message": "failed to start watcher: ..."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_START_FAILED | Watcher startup failed | Backend failed to initialize file watcher | Check path permissions and kernel watch limits |
DELETE /api/v1/watch/watchers/{id}
Section titled “DELETE /api/v1/watch/watchers/{id}”Delete a watcher and stop watching its paths.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Watcher id. |
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>'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.watch.watchers.delete('f47ac10b-58cc-4372-a567-0e02b2c3d479');Response
Section titled “Response”Watcher deleted.
{ "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "deleted": true}No watcher with the given id exists.
{ "code": "WATCHER_NOT_FOUND", "message": "Watcher not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_NOT_FOUND | Watcher not found | No watcher exists for provided id | List watchers and use a valid watcher id |
Streams
Section titled “Streams”GET /api/v1/watch/watchers/{id}/events
Section titled “GET /api/v1/watch/watchers/{id}/events”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Watcher id. |
since_id | query | integer | No | Replay events strictly after this event id. |
since_timestamp | query | string | No | Replay events strictly after this timestamp. Accepts RFC3339 (for example 2026-02-11T15:30:00Z), Unix seconds, or Unix milliseconds. |
page | query | integer | No | Page number (1-based), counted from the oldest event still retained. Ignored when after_id is set. |
limit | query | integer | No | Items per page (1-200). |
after_id | query | integer | No | Continue 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. |
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>'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.watch.events.listIterator('f47ac10b-58cc-4372-a567-0e02b2c3d479', { since_id: 100, limit: 50 })) { console.log(item);}Response
Section titled “Response”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}Invalid query: page/limit out of range or not a whole number (INVALID_PAGINATION), or a bad since_id/since_timestamp/after_id (INVALID_CURSOR); the message names the parameter.
{ "code": "INVALID_PAGINATION", "message": "Limit must be between 1 and 200"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_REQUEST | Invalid request | Request payload is not readable JSON of the expected shape, failed validation, or (on update) gives no field | Check request fields and retry |
INVALID_PAGINATION | Invalid pagination | Page or limit is out of range or not a whole number | Use page >= 1 and limit between 1 and 200 |
INVALID_CURSOR | Invalid cursor | since_id or after_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or since_id and since_timestamp are both given | Pass one cursor, as the last event id received or a timestamp |
No watcher with the given id exists.
{ "code": "WATCHER_NOT_FOUND", "message": "Watcher not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_NOT_FOUND | Watcher not found | No watcher exists for provided id | List watchers and use a valid watcher id |
The requested replay cursor is older than retained history, or a path limit was hit.
{ "code": "HISTORY_GAP", "message": "Requested since_id is older than available replay history"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
LIMIT_EXCEEDED | Resource limit exceeded | Watcher or path limits exceeded | Reduce paths or delete unused watchers |
HISTORY_GAP | Replay history gap | Requested since_id is older than retained replay history | Reconnect without since_id or increase history_memory_limit_bytes |
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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Watcher id. |
since_id | query | integer | No | Replay events strictly after this event id. |
since_timestamp | query | string | No | Replay events strictly after this timestamp. Accepts RFC3339 (for example 2026-02-11T15:30:00Z), Unix seconds, or Unix milliseconds. |
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>'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.watch.events.stream('f47ac10b-58cc-4372-a567-0e02b2c3d479', { since_id: 100 });Response
Section titled “Response”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_eventid: 42data: {"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: lagdata: {"type":"lag","details":"events dropped, history rescanned"}Invalid cursor (INVALID_CURSOR): since_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or both are given; the message names the parameter.
{ "code": "INVALID_CURSOR", "message": "Invalid query parameter `since_id`: invalid digit found in string"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_REQUEST | Invalid request | Request payload is not readable JSON of the expected shape, failed validation, or (on update) gives no field | Check request fields and retry |
INVALID_PAGINATION | Invalid pagination | Page or limit is out of range or not a whole number | Use page >= 1 and limit between 1 and 200 |
INVALID_CURSOR | Invalid cursor | since_id or after_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or since_id and since_timestamp are both given | Pass one cursor, as the last event id received or a timestamp |
No watcher with the given id exists.
{ "code": "WATCHER_NOT_FOUND", "message": "Watcher not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_NOT_FOUND | Watcher not found | No watcher exists for provided id | List watchers and use a valid watcher id |
The requested replay cursor is older than retained history, or a path limit was hit.
{ "code": "HISTORY_GAP", "message": "Requested since_id is older than available replay history"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
LIMIT_EXCEEDED | Resource limit exceeded | Watcher or path limits exceeded | Reduce paths or delete unused watchers |
HISTORY_GAP | Replay history gap | Requested since_id is older than retained replay history | Reconnect without since_id or increase history_memory_limit_bytes |
The watcher already has the maximum number of stream clients attached.
{ "code": "MAX_CLIENTS_REACHED", "message": "Watcher has reached max stream clients"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MAX_CLIENTS_REACHED | Too many clients | Watcher stream client limit reached | Disconnect idle clients or increase max_clients_per_watcher |
GET /api/v1/watch/watchers/{id}/events/ws
Section titled “GET /api/v1/watch/watchers/{id}/events/ws”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Watcher id. |
since_id | query | integer | No | Replay events strictly after this event id. |
since_timestamp | query | string | No | Replay events strictly after this timestamp. Accepts RFC3339 (for example 2026-02-11T15:30:00Z), Unix seconds, or Unix milliseconds. |
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'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-watch-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.watch.events.connect('f47ac10b-58cc-4372-a567-0e02b2c3d479', { since_id: 100 });Response
Section titled “Response”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.
Invalid cursor (INVALID_CURSOR): since_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or both are given; the message names the parameter.
{ "code": "INVALID_CURSOR", "message": "Invalid query parameter `since_id`: invalid digit found in string"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_REQUEST | Invalid request | Request payload is not readable JSON of the expected shape, failed validation, or (on update) gives no field | Check request fields and retry |
INVALID_PAGINATION | Invalid pagination | Page or limit is out of range or not a whole number | Use page >= 1 and limit between 1 and 200 |
INVALID_CURSOR | Invalid cursor | since_id or after_id is not a whole number, since_timestamp is not RFC3339 or unix seconds/milliseconds, or since_id and since_timestamp are both given | Pass one cursor, as the last event id received or a timestamp |
No watcher with the given id exists.
{ "code": "WATCHER_NOT_FOUND", "message": "Watcher not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WATCHER_NOT_FOUND | Watcher not found | No watcher exists for provided id | List watchers and use a valid watcher id |
The requested replay cursor is older than retained history, or a path limit was hit.
{ "code": "HISTORY_GAP", "message": "Requested since_id is older than available replay history"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
LIMIT_EXCEEDED | Resource limit exceeded | Watcher or path limits exceeded | Reduce paths or delete unused watchers |
HISTORY_GAP | Replay history gap | Requested since_id is older than retained replay history | Reconnect without since_id or increase history_memory_limit_bytes |
The watcher already has the maximum number of stream clients attached.
{ "code": "MAX_CLIENTS_REACHED", "message": "Watcher has reached max stream clients"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MAX_CLIENTS_REACHED | Too many clients | Watcher stream client limit reached | Disconnect idle clients or increase max_clients_per_watcher |