Instance control
Section titled “Instance control”Inspect metadata, manage tabs, control the browser lifecycle, and manage cookies for a running browser instance. Every endpoint on this page is served by the browser-{N} control plane of a Hoody container; the parent proxy routes to it and is the only thing callers see from outside.
Cookies
Section titled “Cookies”GET /api/v1/browser/cookies
Section titled “GET /api/v1/browser/cookies”Returns every cookie in the browser context. Pass url to filter by URL.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
start | query | boolean | No | Controls instance creation behavior. Default mode: instances are created automatically. Set to false to prevent creation. When auto-start is disabled globally, set to true to create an instance. Has no schema default; see the page note above. |
url | query | string | No | Filter cookies by URL. Repeating this key in the query string is a 400 VALIDATION_ERROR (url must not be repeated): the parent’s rule is on the key, not on the operation, so it applies here even though this parameter is declared inline. |
curl "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/cookies?url=https%3A%2F%2Fexample.com" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.cookies.list({ url: 'https://example.com' });Response
Section titled “Response”{ "cookies": [ { "name": "session_id", "value": "abc123def456", "domain": "example.com", "path": "/", "httpOnly": true, "secure": true }, { "name": "preference", "value": "dark", "domain": ".example.com", "path": "/", "httpOnly": false, "secure": false } ]}VALIDATION_ERROR. url is not an absolute URL or is repeated, or the instance address (browser_host, browser_port) is missing or malformed. The message names the parameter. On a call that has to start the instance, it is also the answer for a launch parameter found invalid only while the instance starts (for example display, chromiumVersion, or extensions / extensionsDir / extensionsStoreIds without showBrowser=true or with a directory that does not exist): the message names the parameter, no instance is left running, and the start is not retried.
{ "error": "url must be an absolute http or https URL", "code": "VALIDATION_ERROR", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names it | Fix the named parameter and retry |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
NOT_FOUND. No browser instance runs at this address and this request may not create one: start=false, or the operator disabled auto-start and start=true was not sent.
{ "error": "No browser instance runs at this address", "code": "NOT_FOUND", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not Found | No browser instance runs at this address (or, on a tab route, the instance has no tab at all) | Check browser_host/browser_port, or create the instance with /start |
PORT_IN_USE. This call had to start a browser instance, and its browser_port on browser_host is already bound by another process. The request is valid but the port is taken right now: the message names the host and port, nothing was started, and the start is not retried. Pick another browser_port, or stop the process holding it.
{ "error": "browser_port 9223 on 0.0.0.0 is already in use by another process; pick another browser_port or stop that process", "code": "PORT_IN_USE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_IN_USE | Port In Use | The instance’s browser_port is already bound by another process | Pick another browser_port, or stop the process holding it |
This call had to start a browser instance, and the start failed for a reason other than a parameter you sent (those are 400 VALIDATION_ERROR): INSTANCE_START_TIMEOUT (did not start within --worker-timeout; plausibly retryable), BROWSER_LAUNCH_ERROR (the browser process failed to launch) or CHILD_PROCESS_ERROR (the instance failed to initialize). Messages are server-authored. No instance is left running.
{ "error": "Instance did not finish starting within --worker-timeout", "code": "INSTANCE_START_TIMEOUT", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTANCE_START_TIMEOUT | Instance Start Timeout | The child process did not finish starting within --worker-timeout | Retry; if it persists, raise —worker-timeout or check host load |
BROWSER_LAUNCH_ERROR | Browser Launch Failed | The browser process itself failed to launch | Retry with a chromiumVersion the deployment provides: a full version, a major version, or a channel tag such as stable |
CHILD_PROCESS_ERROR | Instance Initialization Failed | The instance failed to initialize for a reason other than the browser launch | Check the server log; the published message is server-authored and deliberately opaque |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport (connection refused, socket reset, DNS); the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running with GET /api/v1/browser/metadata |
This call had to start a browser instance, and another process was still installing the Chrome or Firefox build, or a Chrome Web Store extension, that the instance needs when the wait limit (about 5 minutes) ran out. No instance was started. Retry after the number of seconds in the Retry-After response header. The message names what was being installed: Chrome with its version, Firefox, or the extension id. A failure to take the install lock for any other reason is not this error: it is a 500 initialization failure.
Response includes a Retry-After header (integer, seconds to wait before retrying).
{ "error": "Timed out waiting for another process to finish installing Chrome 136.0.7103.113; try again later", "code": "INSTALL_IN_PROGRESS"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTALL_IN_PROGRESS | Install In Progress | Another process is installing a browser build or extension this instance needs | Retry after the Retry-After delay; the install finishes in the background |
DISPLAY_UNAVAILABLE | Display Unavailable | A headful instance needs a display, and none accepts connections | Start the instance with showBrowser=false (headless), or retry once the display runs |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}POST /api/v1/browser/cookies
Section titled “POST /api/v1/browser/cookies”Adds cookies to the browser context. Each cookie needs a name and a value; provide either an absolute url, or both domain and path.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
start | query | boolean | No | Controls instance creation behavior. Has no schema default; see the page note above. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
cookies | array | Yes | Array of cookie objects to add. Each entry requires name and value; provide either url, or both domain and path (not both forms). Other optional keys are expires, httpOnly, secure, and sameSite (one of Strict, Lax, None). A cookie that breaks these rules is a 400 VALIDATION_ERROR whose message names the field, like cookies[0].sameSite; no cookie of the request is set. |
curl -X POST "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/cookies" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "cookies": [ { "name": "session_id", "value": "abc123def456", "url": "https://example.com", "httpOnly": true, "secure": true } ] }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.cookies.setMany({ cookies: [ { name: 'session_id', value: 'abc123def456', url: 'https://example.com', httpOnly: true, secure: true } ]});Response
Section titled “Response”{ "added": 1}VALIDATION_ERROR. cookies is missing or empty, or one cookie breaks the rules in the request body schema (no name, neither url nor domain with path, an unknown sameSite, …). The message names the field, like cookies[0].sameSite; no cookie of the request is set. Also a missing or malformed instance address (browser_host, browser_port). On a call that has to start the instance, it is also the answer for a launch parameter found invalid only while the instance starts (for example display, chromiumVersion, or extensions / extensionsDir / extensionsStoreIds without showBrowser=true or with a directory that does not exist): the message names the parameter, no instance is left running, and the start is not retried.
{ "error": "cookies[0].sameSite must be one of Strict, Lax, None", "code": "VALIDATION_ERROR", "details": { "field": "cookies[0].sameSite" }}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names it | Fix the named parameter and retry |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. The body is plain text (Forbidden), not the JSON envelope.
ForbiddenNOT_FOUND. No browser instance runs at this address and this request may not create one: start=false, or the operator disabled auto-start and start=true was not sent.
{ "error": "No browser instance runs at this address", "code": "NOT_FOUND", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not Found | No browser instance runs at this address (or, on a tab route, the instance has no tab at all) | Check browser_host/browser_port, or create the instance with /start |
Request timed out. The per-request transport timer (--request-timeout, default 600000 ms) expired while the body was still arriving.
{ "error": "Request timed out before the body finished arriving", "code": "TIMEOUT", "details": {}}PORT_IN_USE. This call had to start a browser instance, and its browser_port on browser_host is already bound by another process. The request is valid but the port is taken right now: the message names the host and port, nothing was started, and the start is not retried. Pick another browser_port, or stop the process holding it.
{ "error": "browser_port 9223 on 0.0.0.0 is already in use by another process; pick another browser_port or stop that process", "code": "PORT_IN_USE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_IN_USE | Port In Use | The instance’s browser_port is already bound by another process | Pick another browser_port, or stop the process holding it |
Payload too large. The request body exceeded the configured cap (HOODY_MAX_BODY_SIZE, default 1 MiB). The connection is closed rather than drained.
{ "error": "Request body exceeded HOODY_MAX_BODY_SIZE (default 1 MiB)", "code": "PAYLOAD_TOO_LARGE", "details": {}}This call had to start a browser instance, and the start failed for a reason other than a parameter you sent (those are 400 VALIDATION_ERROR): INSTANCE_START_TIMEOUT (did not start within --worker-timeout; plausibly retryable), BROWSER_LAUNCH_ERROR (the browser process failed to launch) or CHILD_PROCESS_ERROR (the instance failed to initialize). Messages are server-authored. No instance is left running.
{ "error": "Instance did not finish starting within --worker-timeout", "code": "INSTANCE_START_TIMEOUT", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTANCE_START_TIMEOUT | Instance Start Timeout | The child process did not finish starting within --worker-timeout | Retry; if it persists, raise —worker-timeout or check host load |
BROWSER_LAUNCH_ERROR | Browser Launch Failed | The browser process itself failed to launch | Retry with a chromiumVersion the deployment provides: a full version, a major version, or a channel tag such as stable |
CHILD_PROCESS_ERROR | Instance Initialization Failed | The instance failed to initialize for a reason other than the browser launch | Check the server log; the published message is server-authored and deliberately opaque |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running with GET /api/v1/browser/metadata |
This call had to start a browser instance, and another process was still installing the Chrome or Firefox build, or a Chrome Web Store extension, that the instance needs when the wait limit (about 5 minutes) ran out. No instance was started. Retry after the number of seconds in the Retry-After response header. The message names what was being installed: Chrome with its version, Firefox, or the extension id. A failure to take the install lock for any other reason is not this error: it is a 500 initialization failure.
Response includes a Retry-After header (integer, seconds to wait before retrying).
{ "error": "Timed out waiting for another process to finish installing Chrome 136.0.7103.113; try again later", "code": "INSTALL_IN_PROGRESS"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTALL_IN_PROGRESS | Install In Progress | Another process is installing a browser build or extension this instance needs | Retry after the Retry-After delay; the install finishes in the background |
DISPLAY_UNAVAILABLE | Display Unavailable | A headful instance needs a display, and none accepts connections | Start the instance with showBrowser=false (headless), or retry once the display runs |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}DELETE /api/v1/browser/cookies
Section titled “DELETE /api/v1/browser/cookies”Removes every cookie from the browser context.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
start | query | boolean | No | Controls instance creation behavior. Has no schema default; see the page note above. |
curl -X DELETE "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/cookies" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.cookies.clear();Response
Section titled “Response”{ "cleared": true}VALIDATION_ERROR. The instance address is missing or malformed (browser_host, browser_port). The message names the parameter. On a call that has to start the instance, it is also the answer for a launch parameter found invalid only while the instance starts (for example display, chromiumVersion, or extensions / extensionsDir / extensionsStoreIds without showBrowser=true or with a directory that does not exist): the message names the parameter, no instance is left running, and the start is not retried.
{ "error": "browser_port is missing or malformed", "code": "VALIDATION_ERROR", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names it | Fix the named parameter and retry |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
NOT_FOUND. No browser instance runs at this address and this request may not create one: start=false, or the operator disabled auto-start and start=true was not sent.
{ "error": "No browser instance runs at this address", "code": "NOT_FOUND", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not Found | No browser instance runs at this address (or, on a tab route, the instance has no tab at all) | Check browser_host/browser_port, or create the instance with /start |
PORT_IN_USE. This call had to start a browser instance, and its browser_port on browser_host is already bound by another process. The request is valid but the port is taken right now: the message names the host and port, nothing was started, and the start is not retried. Pick another browser_port, or stop the process holding it.
{ "error": "browser_port 9223 on 0.0.0.0 is already in use by another process; pick another browser_port or stop that process", "code": "PORT_IN_USE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_IN_USE | Port In Use | The instance’s browser_port is already bound by another process | Pick another browser_port, or stop the process holding it |
This call had to start a browser instance, and the start failed for a reason other than a parameter you sent (those are 400 VALIDATION_ERROR): INSTANCE_START_TIMEOUT (did not start within --worker-timeout; plausibly retryable), BROWSER_LAUNCH_ERROR (the browser process failed to launch) or CHILD_PROCESS_ERROR (the instance failed to initialize). Messages are server-authored. No instance is left running.
{ "error": "Instance did not finish starting within --worker-timeout", "code": "INSTANCE_START_TIMEOUT", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTANCE_START_TIMEOUT | Instance Start Timeout | The child process did not finish starting within --worker-timeout | Retry; if it persists, raise —worker-timeout or check host load |
BROWSER_LAUNCH_ERROR | Browser Launch Failed | The browser process itself failed to launch | Retry with a chromiumVersion the deployment provides: a full version, a major version, or a channel tag such as stable |
CHILD_PROCESS_ERROR | Instance Initialization Failed | The instance failed to initialize for a reason other than the browser launch | Check the server log; the published message is server-authored and deliberately opaque |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running with GET /api/v1/browser/metadata |
This call had to start a browser instance, and another process was still installing the Chrome or Firefox build, or a Chrome Web Store extension, that the instance needs when the wait limit (about 5 minutes) ran out. No instance was started. Retry after the number of seconds in the Retry-After response header. The message names what was being installed: Chrome with its version, Firefox, or the extension id. A failure to take the install lock for any other reason is not this error: it is a 500 initialization failure.
Response includes a Retry-After header (integer, seconds to wait before retrying).
{ "error": "Timed out waiting for another process to finish installing Chrome 136.0.7103.113; try again later", "code": "INSTALL_IN_PROGRESS"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTALL_IN_PROGRESS | Install In Progress | Another process is installing a browser build or extension this instance needs | Retry after the Retry-After delay; the install finishes in the background |
DISPLAY_UNAVAILABLE | Display Unavailable | A headful instance needs a display, and none accepts connections | Start the instance with showBrowser=false (headless), or retry once the display runs |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}Introspection and control
Section titled “Introspection and control”GET /api/v1/browser/metadata
Section titled “GET /api/v1/browser/metadata”Retrieves detailed metadata for an existing browser instance, including session information, browser details, operating system, viewport, geolocation, open tabs, and the Chrome DevTools WebSocket URL when available. The webSocketDebuggerUrl field is the WebSocket endpoint for remote debugging.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
start | query | boolean | No | Controls instance creation behavior. Has no schema default; see the page note above. |
curl "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/metadata" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.instances.get();Response
Section titled “Response”{ "instanceGeneration": "gen_4a1c9f2b7d3e5081", "engine": "playwright", "stealth": false, "headless": true, "chromiumBuildId": "136.0.7103.113", "chromiumExecutablePath": "/hoody/storage/hoody-browser/chrome/chrome/linux-136.0.7103.113/chrome-linux64/chrome", "browserExecutablePath": "/hoody/storage/hoody-browser/chrome/chrome/linux-136.0.7103.113/chrome-linux64/chrome", "fingerprintId": "default", "display": ":500", "iframe_url": null, "browser_host": "0.0.0.0", "browser_port": 30001, "sessionId": "s_8e2f4a1c9b3d", "sessionName": "default", "timezoneId": "America/New_York", "locale": "en-US", "viewport": { "width": 1280, "height": 800 }, "viewportSource": "creation", "userAgentString": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.7103.113 Safari/537.36", "browserName": "chromium", "browserFullVersion": "136.0.7103.113", "operatingSystemName": "Linux", "operatingSystemPlatform": "Linux x86_64", "operatingSystemVersion": "5.15.0", "renderingEngine": "chromium", "renderingEngineVersion": "136.0.7103.113", "webSocketDebuggerUrl": "wss://67e89abc123def456789abcd-890abcdef12345678901cdef-cdp-1.node-example-1.containers.hoody.com/devtools/browser/b6e7d6f4-8d1e-4f3a-9b2c-1d4e5f6a7b8d", "devtoolsHttpUrl": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-cdp-1.node-example-1.containers.hoody.com/json/version", "devtoolsFrontendUrl": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-cdp-1.node-example-1.containers.hoody.com", "extensions": [], "useRemoteDebuggingPort": true, "remoteDebuggingAddress": "0.0.0.0", "quicDisabled": true, "http3Disabled": true, "dnsOverHttpsEnabled": true, "dnsOverHttpsUrl": "https://cloudflare-dns.com/dns-query", "tabs": [ { "id": 1, "url": "https://example.com" } ]}{ "instanceGeneration": "gen_4a1c9f2b7d3e5081", "engine": "playwright", "stealth": false, "headless": true, "chromiumBuildId": "136.0.7103.113", "chromiumExecutablePath": "/hoody/storage/hoody-browser/chrome/chrome/linux-136.0.7103.113/chrome-linux64/chrome", "browserExecutablePath": "/hoody/storage/hoody-browser/chrome/chrome/linux-136.0.7103.113/chrome-linux64/chrome", "fingerprintId": "default", "display": ":500", "iframe_url": null, "browser_host": "0.0.0.0", "browser_port": 30001, "sessionId": "s_8e2f4a1c9b3d", "sessionName": "default", "timezoneId": "America/New_York", "locale": "en-US", "viewport": { "width": 1280, "height": 800 }, "viewportSource": "creation", "userAgentString": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.7103.113 Safari/537.36", "browserName": "chromium", "browserFullVersion": "136.0.7103.113", "operatingSystemName": "Linux", "operatingSystemPlatform": "Linux x86_64", "operatingSystemVersion": "5.15.0", "renderingEngine": "chromium", "renderingEngineVersion": "136.0.7103.113", "webSocketDebuggerUrl": null, "devtoolsHttpUrl": null, "devtoolsFrontendUrl": null, "extensions": [], "useRemoteDebuggingPort": false, "remoteDebuggingAddress": "0.0.0.0", "quicDisabled": true, "http3Disabled": true, "dnsOverHttpsEnabled": true, "dnsOverHttpsUrl": "https://cloudflare-dns.com/dns-query", "tabs": [ { "id": 1, "url": "https://example.com" } ]}VALIDATION_ERROR. The instance address is missing or malformed (browser_host, browser_port). The message names the parameter. On a call that has to start the instance, it is also the answer for a launch parameter found invalid only while the instance starts (for example display, chromiumVersion, or extensions / extensionsDir / extensionsStoreIds without showBrowser=true or with a directory that does not exist): the message names the parameter, no instance is left running, and the start is not retried.
{ "error": "browser_port is missing or malformed", "code": "VALIDATION_ERROR", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names it | Fix the named parameter and retry |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
NOT_FOUND. No browser instance runs at this address.
{ "error": "No browser instance runs at this address", "code": "NOT_FOUND", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not Found | No browser instance runs at this address (or, on a tab route, the instance has no tab at all) | Check browser_host/browser_port, or create the instance with /start |
PORT_IN_USE. This call had to start a browser instance, and its browser_port on browser_host is already bound by another process. The request is valid but the port is taken right now: the message names the host and port, nothing was started, and the start is not retried. Pick another browser_port, or stop the process holding it.
{ "error": "browser_port 9223 on 0.0.0.0 is already in use by another process; pick another browser_port or stop that process", "code": "PORT_IN_USE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_IN_USE | Port In Use | The instance’s browser_port is already bound by another process | Pick another browser_port, or stop the process holding it |
This call had to start a browser instance, and the start failed for a reason other than a parameter you sent (those are 400 VALIDATION_ERROR): INSTANCE_START_TIMEOUT (did not start within --worker-timeout; plausibly retryable), BROWSER_LAUNCH_ERROR (the browser process failed to launch) or CHILD_PROCESS_ERROR (the instance failed to initialize). Messages are server-authored. No instance is left running.
{ "error": "Instance did not finish starting within --worker-timeout", "code": "INSTANCE_START_TIMEOUT", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTANCE_START_TIMEOUT | Instance Start Timeout | The child process did not finish starting within --worker-timeout | Retry; if it persists, raise —worker-timeout or check host load |
BROWSER_LAUNCH_ERROR | Browser Launch Failed | The browser process itself failed to launch | Retry with a chromiumVersion the deployment provides: a full version, a major version, or a channel tag such as stable |
CHILD_PROCESS_ERROR | Instance Initialization Failed | The instance failed to initialize for a reason other than the browser launch | Check the server log; the published message is server-authored and deliberately opaque |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running |
This call had to start a browser instance, and another process was still installing the Chrome or Firefox build, or a Chrome Web Store extension, that the instance needs when the wait limit (about 5 minutes) ran out. No instance was started. Retry after the number of seconds in the Retry-After response header. The message names what was being installed: Chrome with its version, Firefox, or the extension id. A failure to take the install lock for any other reason is not this error: it is a 500 initialization failure.
Response includes a Retry-After header (integer, seconds to wait before retrying).
{ "error": "Timed out waiting for another process to finish installing Chrome 136.0.7103.113; try again later", "code": "INSTALL_IN_PROGRESS"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTALL_IN_PROGRESS | Install In Progress | Another process is installing a browser build or extension this instance needs | Retry after the Retry-After delay; the install finishes in the background |
DISPLAY_UNAVAILABLE | Display Unavailable | A headful instance needs a display, and none accepts connections | Start the instance with showBrowser=false (headless), or retry once the display runs |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}GET /api/v1/browser/devtools-url
Section titled “GET /api/v1/browser/devtools-url”Returns the Chrome DevTools WebSocket URL, HTTP discovery URL (/json/version), and DevTools frontend URL for the running instance. These URLs are populated only when the instance was launched with useRemoteDebuggingPort=true and browser=chromium; otherwise each field is null.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
start | query | boolean | No | Accepted for symmetry with the other control endpoints, but this endpoint returns 404 before the auto-start logic and ignores the value. |
curl "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/devtools-url" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.instances.getDevtoolsUrls();Response
Section titled “Response”{ "webSocketDebuggerUrl": "wss://67e89abc123def456789abcd-890abcdef12345678901cdef-cdp-1.node-example-1.containers.hoody.com/devtools/browser/b6e7d6f4-8d1e-4f3a-9b2c-1d4e5f6a7b8d", "devtoolsHttpUrl": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-cdp-1.node-example-1.containers.hoody.com/json/version", "devtoolsFrontendUrl": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-cdp-1.node-example-1.containers.hoody.com"}VALIDATION_ERROR. The instance address is missing or malformed (browser_host, browser_port). The message names the parameter.
{ "error": "browser_port is missing or malformed", "code": "VALIDATION_ERROR", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names it | Fix the named parameter and retry |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
NOT_FOUND. No browser instance runs at this address.
{ "error": "No browser instance runs at this address", "code": "NOT_FOUND", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not Found | No browser instance runs at this address (or, on a tab route, the instance has no tab at all) | Check browser_host/browser_port, or create the instance with /start |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}GET /api/v1/browser/tabs
Section titled “GET /api/v1/browser/tabs”Lists every open tab in the browser instance.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
start | query | boolean | No | Controls instance creation behavior. Has no schema default; see the page note above. |
curl "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/tabs" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.tabs.list();Response
Section titled “Response”[ { "id": 1, "url": "https://example.com", "isActive": true }, { "id": 2, "url": "https://docs.example.com/getting-started", "isActive": false }]VALIDATION_ERROR. The instance address is missing or malformed (browser_host, browser_port). The message names the parameter. On a call that has to start the instance, it is also the answer for a launch parameter found invalid only while the instance starts (for example display, chromiumVersion, or extensions / extensionsDir / extensionsStoreIds without showBrowser=true or with a directory that does not exist): the message names the parameter, no instance is left running, and the start is not retried.
{ "error": "browser_port is missing or malformed", "code": "VALIDATION_ERROR", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names it | Fix the named parameter and retry |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
NOT_FOUND. No browser instance runs at this address.
{ "error": "No browser instance runs at this address", "code": "NOT_FOUND", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not Found | No browser instance runs at this address (or, on a tab route, the instance has no tab at all) | Check browser_host/browser_port, or create the instance with /start |
PORT_IN_USE. This call had to start a browser instance, and its browser_port on browser_host is already bound by another process. The request is valid but the port is taken right now: the message names the host and port, nothing was started, and the start is not retried. Pick another browser_port, or stop the process holding it.
{ "error": "browser_port 9223 on 0.0.0.0 is already in use by another process; pick another browser_port or stop that process", "code": "PORT_IN_USE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_IN_USE | Port In Use | The instance’s browser_port is already bound by another process | Pick another browser_port, or stop the process holding it |
This call had to start a browser instance, and the start failed for a reason other than a parameter you sent (those are 400 VALIDATION_ERROR): INSTANCE_START_TIMEOUT (did not start within --worker-timeout; plausibly retryable), BROWSER_LAUNCH_ERROR (the browser process failed to launch) or CHILD_PROCESS_ERROR (the instance failed to initialize). Messages are server-authored. No instance is left running.
{ "error": "Instance did not finish starting within --worker-timeout", "code": "INSTANCE_START_TIMEOUT", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTANCE_START_TIMEOUT | Instance Start Timeout | The child process did not finish starting within --worker-timeout | Retry; if it persists, raise —worker-timeout or check host load |
BROWSER_LAUNCH_ERROR | Browser Launch Failed | The browser process itself failed to launch | Retry with a chromiumVersion the deployment provides: a full version, a major version, or a channel tag such as stable |
CHILD_PROCESS_ERROR | Instance Initialization Failed | The instance failed to initialize for a reason other than the browser launch | Check the server log; the published message is server-authored and deliberately opaque |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running |
This call had to start a browser instance, and another process was still installing the Chrome or Firefox build, or a Chrome Web Store extension, that the instance needs when the wait limit (about 5 minutes) ran out. No instance was started. Retry after the number of seconds in the Retry-After response header. The message names what was being installed: Chrome with its version, Firefox, or the extension id. A failure to take the install lock for any other reason is not this error: it is a 500 initialization failure.
Response includes a Retry-After header (integer, seconds to wait before retrying).
{ "error": "Timed out waiting for another process to finish installing Chrome 136.0.7103.113; try again later", "code": "INSTALL_IN_PROGRESS"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTALL_IN_PROGRESS | Install In Progress | Another process is installing a browser build or extension this instance needs | Retry after the Retry-After delay; the install finishes in the background |
DISPLAY_UNAVAILABLE | Display Unavailable | A headful instance needs a display, and none accepts connections | Start the instance with showBrowser=false (headless), or retry once the display runs |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}POST /api/v1/browser/tab/close
Section titled “POST /api/v1/browser/tab/close”Closes a specific browser tab by its tab ID. If no tabId is provided, closes the active tab (unless it is the last remaining tab).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
start | query | boolean | No | Controls instance creation behavior. Has no schema default; see the page note above. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
tabId | integer | No | The ID of the tab to close. Omit to close the active tab. |
curl -X POST "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/tab/close" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "tabId": 2 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.tabs.close({ tabId: 2 });Response
Section titled “Response”{ "closed": 2, "remaining": 1}The request tried to close the last remaining tab, or the supplied tabId did not match any open tab. The body is the standard browser_Error JSON envelope with code VALIDATION_ERROR. On a call that has to start the instance, it is also the answer for a launch parameter found invalid only while the instance starts (for example display, chromiumVersion, or extensions / extensionsDir / extensionsStoreIds without showBrowser=true or with a directory that does not exist): the message names the parameter, no instance is left running, and the start is not retried.
INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names the parameter | Read the message: it names the parameter and the constraint that failed |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. The body is plain text (Forbidden), not the JSON envelope.
ForbiddenEither TAB_NOT_FOUND (a tabId was supplied but no such tab is open; details.openTabs lists the open ids) or NOT_FOUND (no tab at all, or no instance with auto-start disabled).
{ "error": "No open tab matches tabId=2", "code": "TAB_NOT_FOUND", "details": { "openTabs": [1] }}Request timed out. The per-request transport timer (--request-timeout, default 600000 ms) expired while the body was still arriving.
{ "error": "Request timed out before the body finished arriving", "code": "TIMEOUT", "details": {}}PORT_IN_USE. This call had to start a browser instance, and its browser_port on browser_host is already bound by another process. The request is valid but the port is taken right now: the message names the host and port, nothing was started, and the start is not retried. Pick another browser_port, or stop the process holding it.
{ "error": "browser_port 9223 on 0.0.0.0 is already in use by another process; pick another browser_port or stop that process", "code": "PORT_IN_USE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_IN_USE | Port In Use | The instance’s browser_port is already bound by another process | Pick another browser_port, or stop the process holding it |
Payload too large. The request body exceeded the configured cap (HOODY_MAX_BODY_SIZE, default 1 MiB). The connection is closed rather than drained.
{ "error": "Request body exceeded HOODY_MAX_BODY_SIZE (default 1 MiB)", "code": "PAYLOAD_TOO_LARGE", "details": {}}This call had to start a browser instance, and the start failed for a reason other than a parameter you sent (those are 400 VALIDATION_ERROR): INSTANCE_START_TIMEOUT (did not start within --worker-timeout; plausibly retryable), BROWSER_LAUNCH_ERROR (the browser process failed to launch) or CHILD_PROCESS_ERROR (the instance failed to initialize). Messages are server-authored. No instance is left running.
{ "error": "Instance did not finish starting within --worker-timeout", "code": "INSTANCE_START_TIMEOUT", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTANCE_START_TIMEOUT | Instance Start Timeout | The child process did not finish starting within --worker-timeout | Retry; if it persists, raise —worker-timeout or check host load |
BROWSER_LAUNCH_ERROR | Browser Launch Failed | The browser process itself failed to launch | Retry with a chromiumVersion the deployment provides: a full version, a major version, or a channel tag such as stable |
CHILD_PROCESS_ERROR | Instance Initialization Failed | The instance failed to initialize for a reason other than the browser launch | Check the server log; the published message is server-authored and deliberately opaque |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running |
This call had to start a browser instance, and another process was still installing the Chrome or Firefox build, or a Chrome Web Store extension, that the instance needs when the wait limit (about 5 minutes) ran out. No instance was started. Retry after the number of seconds in the Retry-After response header. The message names what was being installed: Chrome with its version, Firefox, or the extension id. A failure to take the install lock for any other reason is not this error: it is a 500 initialization failure.
Response includes a Retry-After header (integer, seconds to wait before retrying).
{ "error": "Timed out waiting for another process to finish installing Chrome 136.0.7103.113; try again later", "code": "INSTALL_IN_PROGRESS"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTALL_IN_PROGRESS | Install In Progress | Another process is installing a browser build or extension this instance needs | Retry after the Retry-After delay; the install finishes in the background |
DISPLAY_UNAVAILABLE | Display Unavailable | A headful instance needs a display, and none accepts connections | Start the instance with showBrowser=false (headless), or retry once the display runs |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}GET /api/v1/browser/shutdown
Section titled “GET /api/v1/browser/shutdown”Gracefully shuts down a specific browser instance. Responds 200 as soon as shutdown is initiated; teardown completes asynchronously, so poll /metadata?…&start=false for 404 to confirm the instance is gone. Use /stop for a synchronous kill.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_id | query | string | No | Selects the instance: SDK and CLI clients send the request to the browser-{N} service host. A value that conflicts with the host’s instance is refused with 400 INSTANCE_SELECTOR_CONFLICT. Against a bare server it selects nothing; address instances there with browser_host + browser_port. |
curl "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/shutdown" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.instances.shutdown();Response
Section titled “Response”{ "message": "Instance shutdown successfully"}VALIDATION_ERROR. The instance address is missing or malformed (browser_host, browser_port). The message names the parameter. On a call that has to start the instance, it is also the answer for a launch parameter found invalid only while the instance starts (for example display, chromiumVersion, or extensions / extensionsDir / extensionsStoreIds without showBrowser=true or with a directory that does not exist): the message names the parameter, no instance is left running, and the start is not retried.
{ "error": "browser_port is missing or malformed", "code": "VALIDATION_ERROR", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names it | Fix the named parameter and retry |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
NOT_FOUND. No browser instance runs at this address.
{ "error": "No browser instance runs at this address", "code": "NOT_FOUND", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not Found | No browser instance runs at this address (or, on a tab route, the instance has no tab at all) | Check browser_host/browser_port, or create the instance with /start |
PORT_IN_USE. This call had to start a browser instance, and its browser_port on browser_host is already bound by another process. The request is valid but the port is taken right now: the message names the host and port, nothing was started, and the start is not retried. Pick another browser_port, or stop the process holding it.
{ "error": "browser_port 9223 on 0.0.0.0 is already in use by another process; pick another browser_port or stop that process", "code": "PORT_IN_USE"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_IN_USE | Port In Use | The instance’s browser_port is already bound by another process | Pick another browser_port, or stop the process holding it |
This call had to start a browser instance, and the start failed for a reason other than a parameter you sent (those are 400 VALIDATION_ERROR): INSTANCE_START_TIMEOUT (did not start within --worker-timeout; plausibly retryable), BROWSER_LAUNCH_ERROR (the browser process failed to launch) or CHILD_PROCESS_ERROR (the instance failed to initialize). Messages are server-authored. No instance is left running.
{ "error": "Instance did not finish starting within --worker-timeout", "code": "INSTANCE_START_TIMEOUT", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTANCE_START_TIMEOUT | Instance Start Timeout | The child process did not finish starting within --worker-timeout | Retry; if it persists, raise —worker-timeout or check host load |
BROWSER_LAUNCH_ERROR | Browser Launch Failed | The browser process itself failed to launch | Retry with a chromiumVersion the deployment provides: a full version, a major version, or a channel tag such as stable |
CHILD_PROCESS_ERROR | Instance Initialization Failed | The instance failed to initialize for a reason other than the browser launch | Check the server log; the published message is server-authored and deliberately opaque |
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running |
This call had to start a browser instance, and another process was still installing the Chrome or Firefox build, or a Chrome Web Store extension, that the instance needs when the wait limit (about 5 minutes) ran out. No instance was started. Retry after the number of seconds in the Retry-After response header. The message names what was being installed: Chrome with its version, Firefox, or the extension id. A failure to take the install lock for any other reason is not this error: it is a 500 initialization failure.
Response includes a Retry-After header (integer, seconds to wait before retrying).
{ "error": "Timed out waiting for another process to finish installing Chrome 136.0.7103.113; try again later", "code": "INSTALL_IN_PROGRESS"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSTALL_IN_PROGRESS | Install In Progress | Another process is installing a browser build or extension this instance needs | Retry after the Retry-After delay; the install finishes in the background |
DISPLAY_UNAVAILABLE | Display Unavailable | A headful instance needs a display, and none accepts connections | Start the instance with showBrowser=false (headless), or retry once the display runs |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the instance may or may not have already done the work.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}Viewport policy
Section titled “Viewport policy”GET /api/v1/browser/viewport
Section titled “GET /api/v1/browser/viewport”Returns the instance’s current viewport policy without mutating anything. viewport: null means fixed-viewport emulation is disabled (responsive: the page follows the real window size). source is creation until the first successful POST /api/v1/browser/viewport, then runtime. converged reports whether every live page currently reflects the policy.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_host | query | string | No | Instance host. Optional; must be paired with browser_port. When both are omitted, the single running instance is selected (400 AMBIGUOUS_INSTANCE with more than one). |
browser_port | query | integer | No | Instance port. Optional; must be paired with browser_host. |
curl "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/viewport" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.viewport.get();Response
Section titled “Response”{ "viewport": { "width": 1280, "height": 800 }, "source": "creation", "tabs": 1, "converged": true}Ambiguous instance (browser_host/browser_port omitted with multiple instances running) or invalid parameters.
{ "error": "browser_host/browser_port omitted but more than one instance is running", "code": "AMBIGUOUS_INSTANCE", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names the parameter | Read the message: it names the parameter and the constraint that failed |
AMBIGUOUS_INSTANCE | Ambiguous Instance | browser_host and browser_port were omitted while more than one instance is running | Pass browser_host and browser_port to name the instance |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
No browser instance is running. The body is the standard browser_Error JSON envelope and details.reason is "no_instance".
The parent could not deliver the instance’s reply. UPSTREAM_RESPONSE_TOO_LARGE means the reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). PROXY_ERROR means the request to the instance’s control process failed at the transport; the underlying exception is logged, never published. Both are parent-generated: the instance may or may not have already done the work.
{ "error": "Upstream response exceeded the parent's response cap and was discarded", "code": "UPSTREAM_RESPONSE_TOO_LARGE", "details": {}}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated) | Ask the instance for less data, then retry |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}POST /api/v1/browser/viewport
Section titled “POST /api/v1/browser/viewport”Mutates the running instance’s viewport policy. The body is JSON only.
- Fixed size:
{"viewport": {"width": 1280, "height": 800}}. Width and height are integers in the range 1..8192; no other keys are allowed. - Responsive (follow the real window):
{"viewport": null}.
The policy applies to every currently-open tab; tabs opened later inherit it.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
browser_host | query | string | No | Instance host. Optional; must be paired with browser_port. When both are omitted, the single running instance is selected (400 AMBIGUOUS_INSTANCE with more than one). |
browser_port | query | integer | No | Instance port. Optional; must be paired with browser_host. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
viewport | object | null | Yes | Fixed size {width, height} (integers 1..8192, no other keys), or null for responsive. |
curl -X POST "https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com/api/v1/browser/viewport" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "viewport": { "width": 1280, "height": 800 } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-browser-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.browser.viewport.set({ viewport: { width: 1280, height: 800 }});Response
Section titled “Response”{ "viewport": { "width": 1280, "height": 800 }, "source": "runtime", "tabs": 1, "converged": true}Validation error or ambiguous instance (browser_host/browser_port omitted with multiple instances running).
{ "error": "browser_host/browser_port omitted but more than one instance is running", "code": "AMBIGUOUS_INSTANCE", "details": {}}INSTANCE_SELECTOR_CONFLICT: the request came through a browser-{N} service hostname, which serves browser N only, and a browser_port, port, display, browser_id, browser_host or host in its query or JSON body names a different instance (browser_host is the one the query carries). details.field names the parameter, and nothing was run.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Validation Error | A request parameter failed validation; the message names the parameter | Read the message: it names the parameter and the constraint that failed |
AMBIGUOUS_INSTANCE | Ambiguous Instance | browser_host and browser_port were omitted while more than one instance is running | Pass browser_host and browser_port to name the instance |
INSTANCE_SELECTOR_CONFLICT | Instance Selector Conflict | The request came through a browser-{N} hostname and names a different instance | Call the other instance’s own browser-{N} hostname, or leave the parameter out |
Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. The body is plain text (Forbidden), not the JSON envelope.
ForbiddenNo browser instance is running. The body is the standard browser_Error JSON envelope and details.reason is "no_instance".
Request timed out. The per-request transport timer (--request-timeout, default 600000 ms) expired while the body was still arriving.
{ "error": "Request timed out before the body finished arriving", "code": "TIMEOUT", "details": {}}REQUIRES_RESTART: the instance was created with a fixed viewport; responsive requires recreating it.
{ "error": "Responsive requires an instance created without a fixed viewport", "code": "REQUIRES_RESTART", "details": {}}Payload too large. The request body exceeded the configured cap (HOODY_MAX_BODY_SIZE, default 1 MiB). The connection is closed rather than drained.
{ "error": "Request body exceeded HOODY_MAX_BODY_SIZE (default 1 MiB)", "code": "PAYLOAD_TOO_LARGE", "details": {}}NOT_SUPPORTED: responsive at runtime is Chromium-only.
{ "error": "Responsive viewport is supported on Chromium only", "code": "NOT_SUPPORTED", "details": {}}The policy was accepted but some tabs failed to apply it (details.failedTabs names them; details.converged is false), yielding VIEWPORT_APPLY_INCOMPLETE (child-generated). The parent proxy answers 502 on this route without ever reaching the child for two other reasons: UPSTREAM_RESPONSE_TOO_LARGE (the instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole, never truncated), and PROXY_ERROR (the request to the instance’s control process failed at the transport, with the exception text logged rather than published). On UPSTREAM_RESPONSE_TOO_LARGE the viewport change itself succeeded and only its reply was dropped; re-read the state with GET /api/v1/browser/viewport rather than re-applying blindly.
{ "error": "Policy applied to some tabs; failedTabs lists the rest", "code": "VIEWPORT_APPLY_INCOMPLETE", "details": { "failedTabs": [3], "converged": false }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VIEWPORT_APPLY_INCOMPLETE | Viewport apply incomplete | The policy was accepted but some tabs failed to apply it (details.failedTabs names them; details.converged is false). Child-generated. | Re-read state with GET /api/v1/browser/viewport; close and recreate the failed tabs if needed |
UPSTREAM_RESPONSE_TOO_LARGE | Upstream response too large | The instance’s reply exceeded the parent’s 128 MiB response cap and was discarded whole (never truncated). On this code the viewport change itself succeeded; only its reply was dropped. | Re-read state with GET /api/v1/browser/viewport rather than re-applying blindly |
PROXY_ERROR | Proxy error | The parent could not reach the instance’s control process (connection refused, socket reset, DNS); the exception text is logged but never published | Retry; if it persists, verify the instance is running |
The instance did not answer within the server’s request timeout (--request-timeout, default 600000 ms). Parent-generated, with details.phase proxy and details.outcome unknown: the policy may or may not have been applied, so re-read it with GET /api/v1/browser/viewport.
{ "error": "Instance did not answer within the request timeout", "code": "TIMEOUT", "details": { "phase": "proxy", "outcome": "unknown" }}