Route Management
Section titled “Route Management”These endpoints manage three capabilities of the exec service: Next.js-style URL routing for user-authored scripts, on-the-fly generation of user OpenAPI specs, and lifecycle management of imported SDKs. They all operate on the container’s mounted scripts root (under /hoody/storage/hoody-exec/scripts). Path fields are reported exactly as each endpoint’s spec describes: route discovery reports each entry’s path in a field named file (scripts-root-relative, with baseDir echoed from the request), route resolution reports scriptPath and baseDir (both scripts-root-relative), user-openapi list returns path relative to the scanned directory (for example, path: "api/users.ts" beside a directory that ends in default/1), and SDK responses return path/directory as default/<sdkId>, any middleware the import’s middleware supplied as default/<sdkId>/pre.js and post.js, and marker as the metadata file path default/<sdkId>/.sdk.json (a path, never the inline /* hoody-sdk:... */ comment string). Use the routing endpoints to map a URL to its handler and inspect what a script directory can serve; use the user-openapi endpoints to generate, merge, and validate schemas; use the SDK endpoints to import an external OpenAPI spec, inspect its generated middleware, and delete it.
Route discovery and resolution
Section titled “Route discovery and resolution”POST /api/v1/exec/route/discover
Section titled “POST /api/v1/exec/route/discover”Discover all available routes in the script directory, classified as static, dynamic [param], catch-all [...slug], or optional catch-all [[...path]]. Each entry reports its pattern (relative to baseDir), file path (scripts-root-relative), type, and extracted parameter names, and route: the URL path on which the runtime serves that file for the request’s scope (hostname and execId, from the body or else the Host header, as in route/resolve), or null when no route could be verified: no URL reaches it in that scope, or (rarely, a URL may then still reach it) routes overlap past the checking budget. Set includeMetadata to true to include size and modification time per file.
This endpoint takes no parameters.
Request body
Section titled “Request body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
baseDir | string | No | "" | Scripts-root-relative directory to scan. Pass a per-instance scope like "default/1" to limit discovery to one subdomain/execId pair; empty scans the full root. |
includeMetadata | boolean | No | false | When true, each route entry includes file size and modification time. |
hostname | string | No | — | Hostname scope route is computed for. When omitted, it is taken from the Host header’s subdomain, then defaults to default; lowercased. |
execId | string | No | — | Exec ID scope route is computed for. When omitted, it is taken from the Host header; lowercased. |
Examples
Section titled “Examples”curl -X POST https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/route/discover \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"baseDir":"default/1","includeMetadata":true}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.routes.list({ baseDir: 'default/1', includeMetadata: true });Responses
Section titled “Responses”{ "baseDir": "default/1", "hostname": "default", "execId": "1", "count": 2, "routes": [ { "pattern": "api/users/[id]", "route": "/api/users/[id]", "file": "default/1/api/users/[id].ts", "type": "dynamic", "parameters": ["id"], "metadata": { "size": 512, "modified": "2026-01-01T00:00:00.000Z" } }, { "pattern": "api/users", "route": "/api/users", "file": "default/1/api/users.ts", "type": "static", "parameters": [], "metadata": { "size": 348, "modified": "2026-01-01T00:00:00.000Z" } } ]}| Field | Type | Description |
|---|---|---|
baseDir | string | The directory scanned. |
hostname | string | The hostname scope route was computed for (body hostname, else the Host header’s subdomain, else default; lowercased). |
execId | string | null | The execId scope route was computed for (body execId, else the Host header’s; lowercased), or null for none. |
count | integer | Number of routes returned. |
routes[].pattern | string | The route pattern relative to baseDir (the scripts root when none is given), [param] segments as written. Not a URL unless baseDir is the deployment’s own directory: route is. |
routes[].route | string | null | The URL path on which the runtime serves this file for the request’s scope, [param] segments as written: fill each with your value, a catch-all with one or more segments ([[...x]]: none or more). A value or catch-all segment count that a narrower route also matches goes to that route, which is listed with its own route. null when no route could be verified, which is mostly when no URL reaches the file in this scope (other routes take every URL it matches, it is outside an exclusive tree, it is a .js beside its .ts twin, a search directory of the scope is a symlink, and similar), or when the routes overlap so much that checking this one would take more than a fixed amount of work. A file that only the hostname or root index fallback serves is null too. When unsure the answer is null, never a path that serves another file. |
routes[].file | string | The script file, relative to the scripts directory. |
routes[].type | string | One of static, dynamic, catchall, optional-catchall. |
routes[].parameters | string[] | Parameter names of the dynamic segments. |
routes[].metadata | object | With includeMetadata: the file’s size (size, integer) and modification time (modified, date-time). |
{ "error": "baseDir is not a directory", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “baseDir is not a directory”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. Other messages on this status are composed at runtime and are not listed here. |
details.code, when present, is one of: NOT_A_DIRECTORY.
{ "error": "Forbidden", "code": "ERROR_403", "timestamp": "2026-01-01T00:00:00.000Z", "details": {}}{ "error": "Directory not found", "code": "ERROR_404", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_404 | Not found | The service answers 404 with this code; the message names the cause. | — |
{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/route/resolve
Section titled “POST /api/v1/exec/route/resolve”Resolve a URL path to the script that handles it, using Next.js-style matching in priority order: static, dynamic, catch-all, optional catch-all. The lookup is scoped by hostname and execId and falls back through {hostname}/{execId}/, then {hostname}/, then {execId}/, then root. When nothing matches, suggestions lists up to 3 routes of the same scope near the path (leading folders dropped, or a folder of the deployment added), each with the script it runs.
This endpoint takes no parameters.
Request body
Section titled “Request body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | Yes | — | URL path to resolve, e.g. /api/users/42. A non-string is refused with a 400. |
hostname | string | No | — | Hostname namespace: letters, digits, dots, hyphens and underscores, not . or .. and not a reserved directory name (node_modules, .git, .hoody-cache, _sdk, _hoody). Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header, then defaults to default. Any other value is refused with a 400. |
execId | string | No | — | Exec ID namespace: 1 to 64 letters or digits, with no dots, hyphens or underscores (a hostname cannot carry them). Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
Examples
Section titled “Examples”curl -X POST https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/route/resolve \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"path":"/api/users/42","hostname":"default","execId":"1"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.routes.resolve({ path: '/api/users/42', hostname: 'default', execId: '1' });Responses
Section titled “Responses”{ "matched": true, "path": "/api/users/42", "scriptPath": "default/1/api/users/[id].ts", "routePattern": "api/users/[id]", "parameters": { "id": "42" }, "type": "dynamic", "baseDir": "default/1"}When no route matches, the response carries matched: false and lists the directories the resolver tried:
{ "matched": false, "path": "/api/missing", "hostname": "default", "execId": "1", "triedDirectories": [ "default/1", "default", "1", "" ], "suggestions": []}{ "error": "path is required", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “path is required”, “path must be a string”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. Other messages on this status are composed at runtime and are not listed here. | — |
{ "error": "Forbidden", "code": "ERROR_403", "timestamp": "2026-01-01T00:00:00.000Z", "details": {}}{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/route/test
Section titled “POST /api/v1/exec/route/test”Batch-test multiple URL paths against the routing system in a single request. Each path is resolved with the same priority and scoping rules as route resolve. The response carries per-path results plus aggregate matched and notMatched counts. The first 20 unmatched results carry suggestions.
This endpoint takes no parameters.
Request body
Section titled “Request body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
paths | array of strings | Yes | — | URL paths to resolve, e.g. ["/api/users", "/api/users/42"]. A non-string entry is refused with a 400. |
hostname | string | No | — | Hostname namespace: letters, digits, dots, hyphens and underscores, not . or .. and not a reserved directory name (node_modules, .git, .hoody-cache, _sdk, _hoody). Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header, then defaults to default. Any other value is refused with a 400. |
execId | string | No | — | Exec ID namespace: 1 to 64 letters or digits, with no dots, hyphens or underscores (a hostname cannot carry them). Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
Examples
Section titled “Examples”curl -X POST https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/route/test \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"paths":["/api/users/42","/api/posts","/api/missing"],"hostname":"default","execId":"1"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.routes.test({ paths: ['/api/users/42', '/api/posts', '/api/missing'], hostname: 'default', execId: '1' });Responses
Section titled “Responses”{ "tested": 3, "matched": 2, "notMatched": 1, "results": [ { "path": "/api/users/42", "matched": true, "scriptPath": "default/1/api/users/[id].ts", "parameters": { "id": "42" } }, { "path": "/api/posts", "matched": true, "scriptPath": "default/1/api/posts.ts", "parameters": {} }, { "path": "/api/missing", "matched": false, "suggestions": [] } ]}{ "error": "paths array is required", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “paths array is required”, “paths must be an array of strings”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. Other messages on this status are composed at runtime and are not listed here. | — |
{ "error": "Forbidden", "code": "ERROR_403", "timestamp": "2026-01-01T00:00:00.000Z", "details": {}}{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
User OpenAPI
Section titled “User OpenAPI”These endpoints generate, merge, validate, and serve OpenAPI specifications derived from your script directory.
GET /api/v1/exec/user-openapi/list
Section titled “GET /api/v1/exec/user-openapi/list”List available scripts with their extracted schema information, including route path, schema format (when present), and any dynamic path parameters. The path field of each entry is relative to the scanned directory.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
directory | query | string | No | Script directory to list (absolute or relative to scripts-dir). Default: scripts. |
dir | query | string | No | Alias of directory. Ignored when directory is provided. |
subdomain | query | string | No | Limit scan to scripts under this subdomain. Falls back to the Host header when omitted. |
execId | query | string | No | Limit scan to scripts under this execId. Falls back to the Host header when omitted. |
Examples
Section titled “Examples”curl "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/user-openapi/list?directory=scripts&subdomain=default&execId=1" \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.openapi.listScripts({ directory: 'scripts', subdomain: 'default', execId: '1' });Responses
Section titled “Responses”{ "success": true, "data": { "directory": "scripts", "totalScripts": 2, "withSchemas": 1, "scripts": [ { "path": "default/1/api/users/[id].ts", "routePath": "api/users/[id]", "hasSchema": true, "schemaFormat": "zod", "pathParameters": ["id"] }, { "path": "default/1/api/posts.ts", "routePath": "api/posts", "hasSchema": false } ] }}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
GET /api/v1/exec/user-openapi/schema
Section titled “GET /api/v1/exec/user-openapi/schema”Serve the schema file (Zod or JSON Schema) associated with a single script directly. Provide either file or path as the scripts-root-relative path.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
file | query | string | No | Absolute or scripts-dir-relative path to the target script (e.g. default/api/users/[id].ts). Either file or path must be provided. |
path | query | string | No | Alias of file. Either file or path must be provided. |
subdomain | query | string | No | Resolve file under this subdomain. Falls back to the Host header when omitted. |
execId | query | string | No | Resolve file under this execId. Falls back to the Host header when omitted. |
Examples
Section titled “Examples”curl "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/user-openapi/schema?file=default/1/api/users/[id].ts" \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.openapi.getSchema({ file: 'default/1/api/users/[id].ts' });Responses
Section titled “Responses”{ "format": "zod", "file": "default/1/api/users/[id].ts", "schema": { "type": "object", "properties": { "id": { "type": "string" } }, "required": ["id"] }}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "success": false, "error": "No schema file found for this script", "code": "ERROR_404", "timestamp": "2026-01-01T00:00:00.000Z"}{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
GET /api/v1/exec/user-openapi/spec
Section titled “GET /api/v1/exec/user-openapi/spec”Generate and serve an OpenAPI specification on-the-fly from the script directory. Pass format=yaml to receive YAML instead of the default JSON.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
dir | query | string | No | Script directory to scan (absolute or relative to scripts-dir). Default: scripts. |
directory | query | string | No | Alias of dir. Ignored when dir is provided. |
format | query | string | No | Output format. json (default) or yaml. |
subdomain | query | string | No | Limit scan to scripts under this subdomain. Falls back to the Host header when omitted. |
execId | query | string | No | Limit scan to scripts under this execId. Falls back to the Host header when omitted. |
Examples
Section titled “Examples”curl "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/user-openapi/spec?dir=scripts&format=json&subdomain=default&execId=1" \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.openapi.get({ dir: 'scripts', format: 'json', subdomain: 'default', execId: '1' });Responses
Section titled “Responses”{ "openapi": "3.0.3", "info": { "title": "User scripts", "version": "1.0.0" }, "paths": { "/api/users/{id}": { "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ] } } }}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/user-openapi/generate
Section titled “POST /api/v1/exec/user-openapi/generate”Generate an OpenAPI specification from user scripts and return it inline (without persisting it).
This endpoint takes no parameters.
Request body
Section titled “Request body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
directory | string | No | "scripts" | Directory to scan, relative to the scripts directory. Default: the whole scripts directory, or the namespace when one applies. |
directories | array of strings | No | — | Alternative to directory. Only the first entry is scanned, and only when directory is not given; use /user-openapi/merge to combine several directories. |
subdomain | string | No | — | Subdomain namespace: letters, digits, dots, hyphens and underscores, not made only of dots and not a reserved directory name (node_modules, .git, .hoody-cache, _sdk, _hoody, in any case); it is lowercased. Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
execId | string | No | — | Exec ID namespace: 1 to 64 letters or digits, with no dots, hyphens or underscores (a hostname cannot carry them); it is lowercased. Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
recursive | boolean | No | true | Scan subdirectories. |
includePatterns | array of strings | No | — | File-name globs to include. Default ["*.js", "*.ts"]. |
excludePatterns | array of strings | No | — | File-name globs to skip. Default ["*.test.js", "*.spec.ts", "*.test.ts", "*.spec.js"]. |
title | string | No | "Generated API" | Document info.title. |
description | string | No | "Auto-generated from user scripts" | Document info.description. |
version | string | No | "1.0.0" | Document info.version. |
baseUrl | string | No | "http://localhost:8081" | URL of the document’s only servers entry. |
Examples
Section titled “Examples”curl -X POST https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/user-openapi/generate \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"directory":"scripts","subdomain":"default","execId":"1"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.openapi.generate({ directory: 'scripts', subdomain: 'default', execId: '1' });Responses
Section titled “Responses”{ "success": true, "data": { "openapi": "3.0.3", "info": { "title": "User scripts", "version": "1.0.0" }, "paths": {} }, "meta": { "pathCount": 0, "scanDirectory": "scripts", "generatedAt": "2026-01-01T00:00:00.000Z" }}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/user-openapi/merge
Section titled “POST /api/v1/exec/user-openapi/merge”Merge multiple OpenAPI specifications into a single document.
This endpoint takes no parameters.
Request body
Section titled “Request body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
directories | array of strings | No | — | Directories to generate a document from and merge, relative to the scripts directory. |
specs | array of objects | No | — | OpenAPI documents to merge, as objects; any other entry is refused with a 400. A document without a paths object contributes no paths. |
title | string | No | "Merged API" | Merged document info.title. |
description | string | No | "Combined OpenAPI specification" | Merged document info.description. |
version | string | No | "1.0.0" | Merged document info.version. |
subdomain | string | No | — | Subdomain namespace: letters, digits, dots, hyphens and underscores, not made only of dots and not a reserved directory name (node_modules, .git, .hoody-cache, _sdk, _hoody, in any case); it is lowercased. Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
execId | string | No | — | Exec ID namespace: 1 to 64 letters or digits, with no dots, hyphens or underscores (a hostname cannot carry them); it is lowercased. Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
Examples
Section titled “Examples”curl -X POST https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/user-openapi/merge \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"specs":[{"openapi":"3.0.3","paths":{}},{"openapi":"3.0.3","paths":{}}]}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.openapi.merge({ specs: [{ openapi: '3.0.3', paths: {} }, { openapi: '3.0.3', paths: {} }] });Responses
Section titled “Responses”{ "success": true, "data": { "openapi": "3.0.3", "info": { "title": "Merged specs", "version": "1.0.0" }, "paths": {} }}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/user-openapi/validate
Section titled “POST /api/v1/exec/user-openapi/validate”Validate a script’s schema file and return any errors.
This endpoint takes no parameters.
Request body
Section titled “Request body”At least one of file or path must be provided; they are alternatives, not both.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
file | string | Conditional | — | Script path relative to the scripts directory, e.g. default/api/users.ts. Its .openapi.json companion is the file validated. A non-string is refused with a 400. Must be provided when path is absent. |
path | string | Conditional | — | Alias of file. Ignored when file is provided. A non-string is refused with a 400. Must be provided when file is absent. |
subdomain | string | No | — | Subdomain namespace: letters, digits, dots, hyphens and underscores, not made only of dots and not a reserved directory name (node_modules, .git, .hoody-cache, _sdk, _hoody, in any case); it is lowercased. Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
execId | string | No | — | Exec ID namespace: 1 to 64 letters or digits, with no dots, hyphens or underscores (a hostname cannot carry them); it is lowercased. Surrounding whitespace is ignored. When omitted, null or empty, it is taken from the Host header. Any other value is refused with a 400. |
Examples
Section titled “Examples”curl -X POST https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/user-openapi/validate \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"file":"default/1/api/users/[id].ts"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.openapi.validateSchema({ file: 'default/1/api/users/[id].ts' });Responses
Section titled “Responses”{ "success": true, "data": { "file": "default/1/api/users/[id].ts", "format": "zod", "valid": true }, "errors": []}A failing validation looks like:
{ "success": false, "data": { "file": "default/1/api/users/[id].ts", "format": "zod", "valid": false }, "errors": [ "required field 'id' missing from object schema", "field 'id' is not a string" ]}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "success": false, "error": "No schema file found for this script", "code": "ERROR_404", "timestamp": "2026-01-01T00:00:00.000Z"}{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
SDK management
Section titled “SDK management”Each SDK lives under a path of the form default/<sdkId>/ inside the scripts root. The service writes the proxy, schema and types files for every import. The pre.js and post.js middleware files are written only when the import’s middleware field supplies source for them; an import without middleware writes no pre.js or post.js. The marker field in responses is always a path to the metadata file default/<sdkId>/.sdk.json, never the inline /* hoody-sdk:... */ comment string.
GET /api/v1/exec/sdk/list
Section titled “GET /api/v1/exec/sdk/list”List all imported SDKs. The marker field is the metadata file path (for example default/petstore/.sdk.json).
This endpoint takes no parameters.
Examples
Section titled “Examples”curl https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/sdk/list \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.sdks.list();Responses
Section titled “Responses”{ "sdks": [ { "id": "petstore", "source_url": "https://petstore3.swagger.io/api/v3/openapi.json", "files": 12, "middleware": { "pre": true, "post": true }, "marker": "default/petstore/.sdk.json" } ], "total": 1}{ "error": "Malformed query string", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. | Percent-encoding failed to decode. Use valid %XX escapes. |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
GET /api/v1/exec/sdk/{id}
Section titled “GET /api/v1/exec/sdk/{id}”Describe a single imported SDK by id, including its script directory, the marker file path, the generated pre.js/post.js middleware metadata (when present), and the list of generated endpoint scripts.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | SDK identifier. |
Examples
Section titled “Examples”curl https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/sdk/petstore \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.sdks.get('petstore');Responses
Section titled “Responses”{ "id": "petstore", "type": "sdk", "source_url": "https://petstore3.swagger.io/api/v3/openapi.json", "path": "default/petstore", "marker": "default/petstore/.sdk.json", "middleware": { "pre": { "exists": true, "path": "default/petstore/pre.js", "hash": "f6a1c0e8b2d4f5a7c9e0d1b2a3c4d5e6f7a8b9c0" }, "post": { "exists": true, "path": "default/petstore/post.js", "hash": "1a2b3c4d5e6f70819a0b1c2d3e4f5061728394a5" } }, "files": { "total": 12, "endpoints": 10, "list": [ { "path": "default/petstore/pet/getPetById.ts" } ] }}{ "error": "SDK ID is required", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “SDK ID is required”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.). Other messages on this status are composed at runtime and are not listed here. | — |
{ "error": "SDK not found", "code": "ERROR_404", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_404 | Not found | The service answers 404 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
POST /api/v1/exec/sdk/import
Section titled “POST /api/v1/exec/sdk/import”Import an OpenAPI specification from a URL and generate a managed SDK folder under the scripts root. The response includes a diff summary (new, updated, conflicts, total), a list of files removed because the prior spec no longer produces them, and the resulting SDK record with its directory, pre/post/marker paths.
This endpoint takes no parameters.
Request body
Section titled “Request body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
execId | string | No | — | Exec ID the SDK is imported under: 1 to 64 lowercase letters or digits. When omitted, it is taken from the Host header. |
source_url | string | Yes | — | HTTPS URL of the OpenAPI document to import. It must name a domain (not an IP address), use port 443, and end in .json, .yaml, /documentation/json or /documentation/yaml. |
source_auth | object | No | — | Credentials sent when fetching source_url: either {"type": "bearer", "token": "..."} or {"type": "basic", "username": "...", "password": "..."}. Credentials that could not be sent as given are refused with a 400, never dropped. Other members are ignored. |
middleware | object | No | — | Middleware written next to the generated proxy files. Each member is JavaScript source, written as pre.js or post.js into every directory that receives a proxy. pre is the source of pre.js, post is the source of post.js. A member that is null or an empty string writes nothing. |
magic_comments | object | No | — | Magic-comment directives written into every generated proxy file, keyed by directive name. Accepted members: enabled (boolean), token (shared secret, not [REDACTED]), mode (worker or serverless), timeout (integer ms or duration string), log-level (none, minimal, standard, full, debug), concurrent (boolean or integer), cors (origin strategy), websocket (boolean). Any other key is refused with a 400. |
force | boolean | No | false | When true, overwrite existing generated files and apply other destructive update steps. |
Examples
Section titled “Examples”curl -X POST https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/sdk/import \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"execId":"1","source_url":"https://petstore3.swagger.io/api/v3/openapi.json"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.sdks.import({ execId: '1', source_url: 'https://petstore3.swagger.io/api/v3/openapi.json' });Responses
Section titled “Responses”{ "action": "imported", "summary": { "new": 10, "updated": 2, "conflicts": 0, "total": 12 }, "removed": [], "sdk": { "id": "petstore", "source_url": "https://petstore3.swagger.io/api/v3/openapi.json", "path": "default/petstore", "files": { "endpoints": 10, "pre": "default/petstore/pre.js", "post": "default/petstore/post.js", "marker": "default/petstore/.sdk.json" } }}{ "error": "source_url is required", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “execId is required”, “source_url is required”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.), “Invalid JSON body” (Ensure the request body is valid JSON. Example: a backslash followed by a backtick is not a valid JSON escape.), “Request aborted before the body was fully received”. Other messages on this status are composed at runtime and are not listed here. |
details.code, when present, is one of: SDK_FILE_COLLISION, REDACTED_SECRET_UNRESOLVED, SDK_RESERVED_NAME, SDK_UNROUTABLE_PATH, SDK_SCHEMA_TOO_LARGE.
{ "error": "Request body read timeout", "code": "ERROR_408", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_408 | Request timeout | The service answers 408 with this code; the message names the cause. | — |
{ "error": "SDK already exists", "code": "ERROR_409", "timestamp": "2026-01-01T00:00:00.000Z"}{ "error": "Payload too large", "code": "ERROR_413", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_413 | Payload too large | The service answers 413 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |
DELETE /api/v1/exec/sdk/{id}
Section titled “DELETE /api/v1/exec/sdk/{id}”Delete an imported SDK, removing its directory, all generated endpoint files, its pre.js/post.js middleware (when present), and the .sdk.json marker file. The response reports the marker path, removed directory, and number of files removed.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | SDK identifier. |
Examples
Section titled “Examples”curl -X DELETE https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/sdk/petstore \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.exec.sdks.delete('petstore');Responses
Section titled “Responses”{ "message": "SDK deleted", "removed": { "marker": "default/petstore/.sdk.json", "files": 12, "directory": "default/petstore" }}{ "error": "SDK ID is required", "code": "ERROR_400", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_400 | Bad request | The service answers 400 with this code; the message names the cause. Messages: “SDK ID is required”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.). Other messages on this status are composed at runtime and are not listed here. | — |
{ "error": "SDK not found", "code": "ERROR_404", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_404 | Not found | The service answers 404 with this code; the message names the cause. | — |
{ "error": "Too many distinct clients", "code": "ERROR_429", "timestamp": "2026-01-01T00:00:00.000Z", "retryAfter": 60}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_429 | Too many requests | The service answers 429 with this code; the message names the cause. Messages: “Too many distinct clients”, “Too many requests”. | — |
{ "error": "Internal server error", "code": "ERROR_500", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_500 | Internal server error | The service answers 500 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | — |