id
string — 26-character Crockford base32 ULID (lowercase). Assigned by the server on create.
Proxy hooks intercept container traffic before it reaches the real service. Each container has a per-service list of hooks; within a service, hooks are evaluated in position order and the first matching hook wins. All destructive write operations require an If-Match precondition header to prevent lost-update races against concurrent editors.
A hook describes a traffic match and the script that runs when a request matches.
id
string — 26-character Crockford base32 ULID (lowercase). Assigned by the server on create.
position
integer (min 0) — 0-indexed position in the service array.
match
object — Request matcher.
method: string or string[] — one of *, GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.path: string (max length 257).headers: object<string, string> — header presence/value requirements.script
object — Script reference.
subdomain: string (optional).execId: string (optional).path: string — required, the script path.timeout
integer (1–30000) — Execution timeout in milliseconds.
applies_to
object — Scoping.
groups: string[] (min 1) — group identifiers this hook applies to.GET /api/v1/containers/{id}/proxy/hooksReturns all hooks for the given container, grouped by service, along with the current file_version and etag.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
{ "statusCode": 200, "message": "Proxy hooks listed successfully", "data": { "hooks": { "my-service": [ { "id": "01h2x9z8k7v6c5b4n3m2q1p0r9", "position": 0, "match": { "method": "GET", "path": "/api/v1/users", "headers": { "x-api-version": "2" } }, "script": { "subdomain": "auth-mock", "execId": "01h2x9z8k7v6c5b4n3m2q1p0r8", "path": "/hooks/redirect.js" }, "timeout": 5000, "applies_to": { "groups": ["group_alpha"] } } ], "billing": [ { "id": "01h2x9z8k7v6c5b4n3m2q1p0t0", "position": 0, "match": { "method": "POST", "path": "/invoices" }, "script": { "path": "/hooks/invoice-stub.js" }, "timeout": 2000 } ] }, "file_version": 42, "etag": "file:v42" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
const { data } = await client.api.proxyHooks.listContainerProxyHooks({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9"});GET /api/v1/containers/{id}/proxy/hooks/{service}Returns the ordered hook array for a single service. First-match-wins within this list.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
service | path | string | Yes | Service name |
{ "statusCode": 200, "message": "Service hooks listed successfully", "data": { "service": "my-service", "hooks": [ { "id": "01h2x9z8k7v6c5b4n3m2q1p0r9", "position": 0, "match": { "method": "GET", "path": "/api/v1/users", "headers": { "x-api-version": "2" } }, "script": { "subdomain": "auth-mock", "execId": "01h2x9z8k7v6c5b4n3m2q1p0r8", "path": "/hooks/redirect.js" }, "timeout": 5000, "applies_to": { "groups": ["group_alpha"] } } ], "file_version": 42, "etag": "file:v42" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
const { data } = await client.api.proxyHooks.listContainerProxyServiceHooks({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9", service: "my-service"});GET /api/v1/containers/{id}/proxy/hooks/{service}/{hookId}Returns a single hook by id.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
service | path | string | Yes | Service name |
hookId | path | string | Yes | 26-char Crockford base32 ULID (lowercase) |
{ "statusCode": 200, "message": "Hook retrieved successfully", "data": { "hook": { "id": "01h2x9z8k7v6c5b4n3m2q1p0r9", "position": 0, "match": { "method": "GET", "path": "/api/v1/users", "headers": { "x-api-version": "2" } }, "script": { "subdomain": "auth-mock", "execId": "01h2x9z8k7v6c5b4n3m2q1p0r8", "path": "/hooks/redirect.js" }, "timeout": 5000, "applies_to": { "groups": ["group_alpha"] } }, "file_version": 42, "etag": "file:v42" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
const { data } = await client.api.proxyHooks.getContainerProxyHook({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9", service: "my-service", hookId: "01h2x9z8k7v6c5b4n3m2q1p0r9"});POST /api/v1/containers/{id}/proxy/hooks/{service}Create a new hook under a service. Requires If-Match: file:v{N} header. Omit position to append; include it to insert at a specific 0-indexed position.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
service | path | string | Yes | Service name |
if-match | header | string | No | file:v{N} ETag precondition |
| Name | Type | Required | Description |
|---|---|---|---|
match | object | Yes | Request matcher (method, path, headers). |
script | object | Yes | Script reference. path is required inside. |
timeout | integer | No | Execution timeout in milliseconds (1–30000). |
applies_to | object | No | Scoping via groups (array, min 1). |
position | integer | No | 0-indexed insertion position. Omit to append. |
{ "match": { "method": "POST", "path": "/api/v1/orders", "headers": { "content-type": "application/json" } }, "script": { "subdomain": "orders-mock", "execId": "01h2x9z8k7v6c5b4n3m2q1p0r8", "path": "/hooks/order-redirect.js" }, "timeout": 3000, "applies_to": { "groups": ["group_alpha", "group_beta"] }, "position": 0}{ "statusCode": 201, "message": "Hook created successfully", "data": { "hook": { "id": "01h2x9z8k7v6c5b4n3m2q1p0r9", "position": 0, "match": { "method": "POST", "path": "/api/v1/orders", "headers": { "content-type": "application/json" } }, "script": { "subdomain": "orders-mock", "execId": "01h2x9z8k7v6c5b4n3m2q1p0r8", "path": "/hooks/order-redirect.js" }, "timeout": 3000, "applies_to": { "groups": ["group_alpha", "group_beta"] } }, "file_version": 43, "etag": "file:v43" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
{ "statusCode": 412, "error": "Precondition Failed", "message": "etag_mismatch"}{ "statusCode": 422, "error": "Validation Error", "message": "Invalid hook"}{ "statusCode": 428, "error": "Precondition Required", "message": "If-Match header required for this operation"}const { data } = await client.api.proxyHooks.addContainerProxyHook({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9", service: "my-service", "if-match": "file:v42", data: { match: { method: "POST", path: "/api/v1/orders", headers: { "content-type": "application/json" } }, script: { subdomain: "orders-mock", execId: "01h2x9z8k7v6c5b4n3m2q1p0r8", path: "/hooks/order-redirect.js" }, timeout: 3000, applies_to: { groups: ["group_alpha", "group_beta"] }, position: 0 }});PUT /api/v1/containers/{id}/proxy/hooks/{service}/{hookId}Full-replace a hook, preserving its id and position. Requires If-Match. This operation is also reachable via PATCH on the same path; the SDK method below issues PUT.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
service | path | string | Yes | Service name |
hookId | path | string | Yes | 26-char Crockford base32 ULID (lowercase) |
if-match | header | string | No | file:v{N} ETag precondition |
| Name | Type | Required | Description |
|---|---|---|---|
match | object | Yes | Request matcher (method, path, headers). |
script | object | Yes | Script reference. path is required inside. |
timeout | integer | No | Execution timeout in milliseconds (1–30000). |
applies_to | object | No | Scoping via groups (array, min 1). |
position | integer | No | 0-indexed insertion position (POST only). |
{ "match": { "method": "GET", "path": "/api/v1/users", "headers": { "x-api-version": "2" } }, "script": { "subdomain": "auth-mock", "execId": "01h2x9z8k7v6c5b4n3m2q1p0r8", "path": "/hooks/redirect-v2.js" }, "timeout": 7500, "applies_to": { "groups": ["group_alpha"] }}{ "statusCode": 200, "message": "Hook updated successfully", "data": { "hook": { "id": "01h2x9z8k7v6c5b4n3m2q1p0r9", "position": 0, "match": { "method": "GET", "path": "/api/v1/users", "headers": { "x-api-version": "2" } }, "script": { "subdomain": "auth-mock", "execId": "01h2x9z8k7v6c5b4n3m2q1p0r8", "path": "/hooks/redirect-v2.js" }, "timeout": 7500, "applies_to": { "groups": ["group_alpha"] } }, "file_version": 44, "etag": "file:v44" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
{ "statusCode": 412, "error": "Precondition Failed", "message": "etag_mismatch"}{ "statusCode": 422, "error": "Validation Error", "message": "Invalid hook"}{ "statusCode": 428, "error": "Precondition Required", "message": "If-Match header required for this operation"}const { data } = await client.api.proxyHooks.updateContainerProxyHook({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9", service: "my-service", hookId: "01h2x9z8k7v6c5b4n3m2q1p0r9", "if-match": "file:v42", data: { match: { method: "GET", path: "/api/v1/users", headers: { "x-api-version": "2" } }, script: { subdomain: "auth-mock", execId: "01h2x9z8k7v6c5b4n3m2q1p0r8", path: "/hooks/redirect-v2.js" }, timeout: 7500, applies_to: { groups: ["group_alpha"] } }});PATCH /api/v1/containers/{id}/proxy/hooks/{service}/{hookId}/positionAtomically move a hook to a new 0-indexed position within its service. Body: { position: N }. Requires If-Match.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
service | path | string | Yes | Service name |
hookId | path | string | Yes | 26-char Crockford base32 ULID (lowercase) |
if-match | header | string | No | file:v{N} ETag precondition |
| Name | Type | Required | Description |
|---|---|---|---|
position | integer | Yes | 0-indexed target position (min 0). |
{ "position": 2}{ "statusCode": 200, "message": "Hook moved successfully", "data": { "hook": { "id": "01h2x9z8k7v6c5b4n3m2q1p0r9", "position": 2, "match": { "method": "GET", "path": "/api/v1/users" }, "script": { "path": "/hooks/redirect.js" }, "timeout": 5000 }, "file_version": 45, "etag": "file:v45" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
{ "statusCode": 412, "error": "Precondition Failed", "message": "etag_mismatch"}{ "statusCode": 428, "error": "Precondition Required", "message": "If-Match header required for this operation"}const { data } = await client.api.proxyHooks.moveContainerProxyHook({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9", service: "my-service", hookId: "01h2x9z8k7v6c5b4n3m2q1p0r9", "if-match": "file:v44", data: { position: 2 }});DELETE /api/v1/containers/{id}/proxy/hooks/{service}Remove every hook registered under a service. Requires If-Match.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
service | path | string | Yes | Service name |
if-match | header | string | No | file:v{N} ETag precondition |
{ "statusCode": 200, "message": "Service hooks cleared", "data": { "removed": 3, "file_version": 46, "etag": "file:v46" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
{ "statusCode": 412, "error": "Precondition Failed", "message": "etag_mismatch"}{ "statusCode": 428, "error": "Precondition Required", "message": "If-Match header required for this operation"}const { data } = await client.api.proxyHooks.clearContainerProxyServiceHooks({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9", service: "my-service", "if-match": "file:v45"});DELETE /api/v1/containers/{id}/proxy/hooks/{service}/{hookId}Delete a single hook. Requires If-Match.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Container ID |
service | path | string | Yes | Service name |
hookId | path | string | Yes | 26-char Crockford base32 ULID (lowercase) |
if-match | header | string | No | file:v{N} ETag precondition |
{ "statusCode": 200, "message": "Hook removed", "data": { "file_version": 47, "etag": "file:v47" }}{ "statusCode": 404, "error": "Not Found", "message": "Resource not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Hook or service not found | The hook id, service, or container does not exist, or the service is reject-listed | Verify the service name is not reject-listed (logs, proxy, workspaces) and that the hook id exists |
VALIDATION_ERROR | Validation error | Request body violates hook schema, caps, or referential integrity | Check error details and correct the body shape; ensure applies_to.groups references defined groups |
PRECONDITION_REQUIRED | If-Match required | Destructive writes require an If-Match: file:v{N} header | Fetch the resource first to obtain the ETag and resend with If-Match |
PRECONDITION_FAILED | ETag mismatch | The If-Match header does not match the current file_version | Re-fetch the resource to get the current ETag and retry |
{ "statusCode": 412, "error": "Precondition Failed", "message": "etag_mismatch"}{ "statusCode": 428, "error": "Precondition Required", "message": "If-Match header required for this operation"}const { data } = await client.api.proxyHooks.removeContainerProxyHook({ id: "container_01h2x9z8k7v6c5b4n3m2q1p0r9", service: "my-service", hookId: "01h2x9z8k7v6c5b4n3m2q1p0r9", "if-match": "file:v46"});