Monitoring & Performance
Section titled “Monitoring & Performance”description: Monitor stats, active requests, script performance, and system health.
The Monitoring & Performance endpoints expose runtime telemetry for the exec service: process health, active and historical HTTP/WebSocket traffic, per-script performance, Prometheus exposition, and controlled restarts. Use them to power dashboards, set alerts, and drain traffic safely before a deploy.
Script paths in responses are display paths relative to the scripts root. A script for the default host on exec instance 1 named checkout.ts appears as default/1/checkout.ts, not as an absolute filesystem path.
Health
Section titled “Health”GET /api/v1/exec/health
Section titled “GET /api/v1/exec/health”Liveness probe. Returns the process status, a memory snapshot, the PID, the open file descriptor count, and the peer IP of the probing request.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/healthimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.kit.getHealth();Response
Section titled “Response”{ "status": "ok", "service": "hoody-exec", "built": "2026-01-12T08:30:00.000Z", "started": "2026-01-15T14:22:01.000Z", "memory": { "rss": 134217728, "heap": 67108864 }, "fds": 42, "pid": 17, "ip": "10.0.0.5", "userAgent": "kube-probe/1.29"}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
Monitor
Section titled “Monitor”GET /api/v1/exec/monitor/active-requests
Section titled “GET /api/v1/exec/monitor/active-requests”Returns HTTP requests currently being executed by scripts, including the elapsed time per request and the originating script path.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/monitor/active-requestsimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.kit.listRequests();Response
Section titled “Response”{ "count": 2, "active": [ { "executionId": "01HMZ9C8R3K4QJ7WQX2N5TDPAB", "scriptPath": "default/1/checkout.ts", "hostname": "shop.example.com", "clientIp": "203.0.113.41", "method": "POST", "url": "/api/v1/exec/run", "startedAt": "2026-01-15T14:22:18.000Z", "duration": 1284 }, { "executionId": "01HMZ9C8R3K4QJ7WQX2N5TDPAC", "scriptPath": "default/1/webhook.ts", "hostname": "api.example.com", "clientIp": "198.51.100.7", "method": "POST", "url": "/api/v1/exec/run", "startedAt": "2026-01-15T14:22:21.000Z", "duration": 412 } ]}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
GET /api/v1/exec/monitor/scripts
Section titled “GET /api/v1/exec/monitor/scripts”Lists every tracked script with its HTTP and WebSocket counters, latency percentiles, active concurrency, and the most recent errors. Supports pagination and sorting.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Max number of scripts to return. Clamped to [1, 500]. Default 100. |
sort | query | string | No | Sort key. lastActivity (default) sorts by most recent activity; other keys sort descending by the matching metric. Allowed values: lastActivity, requests, errors, p95, ws_active. Default lastActivity. |
curl "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/monitor/scripts?limit=50&sort=p95"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.scripts.listStats({ limit: 50, sort: 'p95' });Response
Section titled “Response”{ "count": 2, "total": 2, "scripts": [ { "scriptPath": "default/1/checkout.ts", "hostname": "shop.example.com", "vmCached": true, "sharedStateBytes": 4096, "activeHttp": 3, "activeWs": 0, "concurrentRunning": 1, "http": { "total": 1024, "success": 1018, "errors": 6, "meanDurationMs": 12.4, "p50DurationMs": 8, "p95DurationMs": 42, "maxDurationMs": 318 }, "ws": { "opened": 0, "closed": 0, "normalCloses": 0, "abnormalCloses": 0, "meanSessionMs": 0, "maxSessionMs": 0 }, "recentErrors": [ { "timestamp": "2026-01-15T14:21:54.000Z", "statusCode": 502, "message": "Upstream fetch failed: connection reset", "executionId": "01HMZ9C8R3K4QJ7WQX2N5TDPAA" } ], "firstSeenAt": "2026-01-15T13:00:00.000Z", "lastActivityAt": "2026-01-15T14:22:18.000Z" }, { "scriptPath": "default/1/webhook.ts", "hostname": "api.example.com", "vmCached": true, "sharedStateBytes": null, "activeHttp": 1, "activeWs": 2, "concurrentRunning": 1, "http": { "total": 512, "success": 510, "errors": 2, "meanDurationMs": 7.1, "p50DurationMs": 5, "p95DurationMs": 18, "maxDurationMs": 122 }, "ws": { "opened": 28, "closed": 26, "normalCloses": 25, "abnormalCloses": 1, "meanSessionMs": 14500, "maxSessionMs": 88200 }, "recentErrors": [], "firstSeenAt": "2026-01-15T12:00:00.000Z", "lastActivityAt": "2026-01-15T14:22:21.000Z" } ]}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
GET /api/v1/exec/monitor/stats
Section titled “GET /api/v1/exec/monitor/stats”Returns process-wide counters: uptime, memory, per-script and per-VM cache sizes, request totals, rolling per-second rates, WebSocket counters, cron counters, and the count of dropped per-script metric entries.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/monitor/statsimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.kit.getStats();Response
Section titled “Response”{ "uptime": 7384, "memory": { "used": 67108864, "total": 134217728, "percentage": 50, "rss": 134217728, "external": 4194304 }, "cache": { "scripts": 12, "vms": 8, "sharedStates": 6, "activeWsHostnames": 3 }, "requests": { "total": 1536, "success": 1528, "errors": 8, "activeHttp": 4, "perSecond": 0.21, "per1m": 4.2, "per5m": 3.9, "per15m": 3.1 }, "websocket": { "opened": 28, "closed": 26, "active": 2, "normalCloses": 25, "abnormalCloses": 1 }, "cron": { "fires": 144, "errors": 2, "active": 0, "wrapperActive": 0 }, "droppedScripts": 0, "sinceMs": 1736950921000}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/monitor/script-performance
Section titled “POST /api/v1/exec/monitor/script-performance”Returns the full performance record for a single script, including HTTP and WebSocket counters, latency percentiles, recent durations, and active concurrency.
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
scriptPath | string | No | Script to report on, as /monitor/scripts lists it (relative to the scripts directory) or as an absolute path. An unknown script returns {"metrics": {}}; a non-string value is refused with a 400. |
{ "scriptPath": "default/1/checkout.ts"}curl -X POST \ -H 'Content-Type: application/json' \ -d '{ "scriptPath": "default/1/checkout.ts" }' \ https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/monitor/script-performanceimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.scripts.getStats({ scriptPath: 'default/1/checkout.ts' });Response
Section titled “Response”{ "metrics": { "scriptPath": "default/1/checkout.ts", "period": "lifetime", "http": { "total": 1024, "success": 1018, "errors": 6, "meanDurationMs": 12.4, "p50DurationMs": 8, "p95DurationMs": 42, "maxDurationMs": 318, "recentDurationsMs": [11, 9, 14, 8, 12, 7, 22, 18] }, "ws": { "opened": 0, "closed": 0, "normalCloses": 0, "abnormalCloses": 0, "meanSessionMs": 0, "maxSessionMs": 0 }, "activeHttp": 3, "activeWs": 0, "firstSeenAt": "2026-01-15T13:00:00.000Z", "lastActivityAt": "2026-01-15T14:22:18.000Z" }}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. Other messages on this status are composed at runtime and are not listed here. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
GET /api/v1/exec/monitor/metrics
Section titled “GET /api/v1/exec/monitor/metrics”Prometheus 0.0.4 text exposition. Includes per-script and aggregate HTTP counters and duration histograms, WebSocket connection and close counters, exporter error counters, and the standard process_start_time_seconds.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/monitor/metricsimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.kit.getMetrics();Response
Section titled “Response”# HELP hoody_exec_http_requests_total Total HTTP requests handled by scripts# TYPE hoody_exec_http_requests_total counterhoody_exec_http_requests_total{script="default/1/checkout.ts",method="POST",status="200"} 1018hoody_exec_http_requests_total{script="default/1/checkout.ts",method="POST",status="502"} 6hoody_exec_http_requests_total{script="default/1/webhook.ts",method="GET",status="200"} 510
# HELP hoody_exec_http_errors_total Total HTTP errors (status >= 400) handled by scripts# TYPE hoody_exec_http_errors_total counterhoody_exec_http_errors_total{script="default/1/checkout.ts",method="POST",status="502"} 6
# HELP hoody_exec_http_duration_ms Script HTTP request duration in milliseconds (per-script)# TYPE hoody_exec_http_duration_ms histogramhoody_exec_http_duration_ms_bucket{script="default/1/checkout.ts",le="10"} 612hoody_exec_http_duration_ms_bucket{script="default/1/checkout.ts",le="50"} 988hoody_exec_http_duration_ms_bucket{script="default/1/checkout.ts",le="100"} 1015hoody_exec_http_duration_ms_bucket{script="default/1/checkout.ts",le="+Inf"} 1024hoody_exec_http_duration_ms_sum{script="default/1/checkout.ts"} 12697.6hoody_exec_http_duration_ms_count{script="default/1/checkout.ts"} 1024
# HELP hoody_exec_http_duration_ms_global Script HTTP request duration in milliseconds (aggregate across all scripts)# TYPE hoody_exec_http_duration_ms_global histogramhoody_exec_http_duration_ms_global_bucket{le="10"} 920hoody_exec_http_duration_ms_global_bucket{le="50"} 1500hoody_exec_http_duration_ms_global_bucket{le="100"} 1530hoody_exec_http_duration_ms_global_bucket{le="+Inf"} 1536hoody_exec_http_duration_ms_global_sum 16301.6hoody_exec_http_duration_ms_global_count 1536
# HELP hoody_exec_ws_connections_active Active WebSocket connections per hostname# TYPE hoody_exec_ws_connections_active gaugehoody_exec_ws_connections_active{hostname="api.example.com"} 2
# HELP hoody_exec_ws_closes_total Total WebSocket close events# TYPE hoody_exec_ws_closes_total counterhoody_exec_ws_closes_total{hostname="api.example.com",reason="normal"} 25hoody_exec_ws_closes_total{hostname="api.example.com",reason="abnormal"} 1
# HELP hoody_exec_metrics_errors_total Total errors emitted by the metrics exporter# TYPE hoody_exec_metrics_errors_total counterhoody_exec_metrics_errors_total 0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds# TYPE process_start_time_seconds gaugeprocess_start_time_seconds 1736950921{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
# Prometheus exporter disabled (server started with --prometheus off){ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
# Metrics not initialised yetSystem
Section titled “System”GET /api/v1/exec/system/restart-status
Section titled “GET /api/v1/exec/system/restart-status”Reports whether the server can be restarted right now, the current uptime, the count of in-flight requests, and the list of in-flight management requests. Poll this endpoint before issuing a restart to confirm restartReady is true.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/system/restart-statusimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.kit.getStatus();Response
Section titled “Response”{ "canRestart": true, "uptime": 7384, "uptimeFormatted": "2h 3m 4s", "activeRequests": 4, "managementRequests": 0, "active": [], "restartReady": true}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/system/restart
Section titled “POST /api/v1/exec/system/restart”Triggers a server restart. A graceful restart drains in-flight requests up to drainTimeoutMs before exiting; a non-graceful restart exits immediately. The endpoint returns 202 Accepted once the restart sequence has been accepted.
Request body
Section titled “Request body”| Name | Type | Required | Default | Description |
|---|---|---|---|---|
graceful | boolean | No | true | When true, drain in-flight requests before restarting. |
drainTimeoutMs | integer | No | 5000 | Maximum time in ms to wait for in-flight requests to drain before forcing exit. Zero when the restart is not graceful. |
reason | string | No | "API restart request" | Free-form reason recorded in the restart log. |
{ "graceful": true, "drainTimeoutMs": 5000, "reason": "Deploy v1.42.0"}curl -X POST \ -H 'Content-Type: application/json' \ -d '{ "graceful": true, "drainTimeoutMs": 5000, "reason": "Deploy v1.42.0" }' \ https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/system/restartimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.kit.restart({ graceful: true, drainTimeoutMs: 5000, reason: 'Deploy v1.42.0'});Response
Section titled “Response”{ "status": "restarting", "graceful": true, "drainTimeoutMs": 5000, "activeRequests": 4, "reason": "Deploy v1.42.0", "message": "Restart accepted, draining in-flight requests."}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Restart function not initialized", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Messages: “Restart function not initialized”, “Internal server error”. Other messages on this status are composed at runtime and are not listed here. | — |