Script Templates
Section titled “Script Templates”The script templates API lets you list, preview, generate, and manage script templates on a Hoody container. Use it to discover the templates that ship with the platform, render a script from a template by supplying the variables it asks for, save the rendered script to the container’s scripts directory, and maintain your own library of custom templates stored under _hoody/templates/.
All endpoints run on the container’s exec host.
List and preview templates
Section titled “List and preview templates”Use these endpoints to discover what templates are available, and to inspect a single template’s source, metadata, and rendered code before generating from it.
GET /api/v1/exec/templates/list
Section titled “GET /api/v1/exec/templates/list”List available templates, optionally filtered by category or source. By default, both built-in and custom templates are returned.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
category | query | string | No | Filter templates to a single metadata category (e.g. api, utility). Omit to list all categories. |
includeBuiltin | query | boolean | No | Include built-in templates in the result set. Default true. Accepts true/false/1/0. |
includeCustom | query | boolean | No | Include user-supplied templates (from _hoody/templates/) in the result set. Default true. |
Example request
Section titled “Example request”curl -X GET "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/templates/list?includeBuiltin=true&includeCustom=true" \ -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 });
const result = await client.exec.templates.list({ includeBuiltin: true, includeCustom: true });Responses
Section titled “Responses”{ "count": 2, "templates": [ { "name": "fetch-items", "category": "api", "description": "Fetch a list of items from an HTTP API and write the results to a file.", "tags": ["http", "fetch"], "params": ["url", "destination"], "type": "builtin" }, { "name": "my-template", "category": "custom", "description": "Custom template for a recurring task.", "tags": ["custom"], "params": ["name"], "type": "custom" } ]}{ "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”. | None |
{ "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. | None |
GET /api/v1/exec/templates/preview
Section titled “GET /api/v1/exec/templates/preview”Read a template’s source and metadata, optionally with its params substituted by the supplied variables. Use this to confirm what a template will produce before calling templates/generate.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | query | string | Yes | Template name, as templates/generate and templates/preview take it. |
variables | query | string | No | JSON-encoded variables to substitute into the template’s params. |
Example request
Section titled “Example request”curl -X GET "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/templates/preview?name=fetch-items&variables=%7B%22url%22%3A%22https%3A%2F%2Fapi.example.com%2Fitems%22%7D" \ -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 });
const result = await client.exec.templates.preview({ name: 'fetch-items', variables: JSON.stringify({ url: 'https://api.example.com/items' })});Responses
Section titled “Responses”{ "template": { "name": "fetch-items", "metadata": { "category": "api", "tags": ["http", "fetch"], "description": "Fetch a list of items from an HTTP API and write the results to a file.", "params": ["url", "destination"] }, "code": "export default async function() {\n const items = await fetch('https://api.example.com/items').then(r => r.json());\n console.log(items);\n}", "originalCode": "export default async function({url}) {\n const items = await fetch(url).then(r => r.json());\n console.log(items);\n}", "substituted": true }}{ "error": "name parameter 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: “name parameter is required”, “Invalid template name: only letters, digits, dots, hyphens, and underscores (max 64 chars)”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.). | None |
{ "error": "Template 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. | None |
{ "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”. | None |
{ "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. | None |
Generate from template
Section titled “Generate from template”POST /api/v1/exec/templates/generate
Section titled “POST /api/v1/exec/templates/generate”Render a script from a template by supplying its variables. When saveFile is true, the rendered script is written to outputPath under the call’s scope, and the response carries the resolved path.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
execId | query | string | No | Optional execution scope. When provided, relative paths resolve under default/{execId}/ unless subdomain is also set. Query value takes precedence over body. Lowercase alphanumeric, no hyphens. |
exec_id | query | string | No | Alias for execId (snake_case). Lowercase alphanumeric, no hyphens. |
subdomain | query | string | No | Optional subdomain namespace used with execId for path resolution. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Template name: a built-in one or a custom template’s (see templates/list). |
variables | object | No | Values for the template’s params, substituted into its code. |
outputPath | string | No | Where to save the script when saveFile is true. Resolves under the call’s scope as <subdomain|default>/<execId>/<outputPath>. |
saveFile | boolean | No | When true, write the generated script to outputPath (required then). When false (the default), only return the code. Default false. |
execId | string | No | Optional execution scope in request body. Query execId/exec_id takes precedence when both are provided. Lowercase alphanumeric, no hyphens. |
exec_id | string | No | Alias for execId (snake_case). Lowercase alphanumeric, no hyphens. |
subdomain | string | No | Optional subdomain namespace used with execId for path resolution. |
Example request
Section titled “Example request”curl -X POST "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/templates/generate" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "fetch-items", "variables": { "url": "https://api.example.com/items" }, "outputPath": "fetch-items.ts", "saveFile": 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 });
const result = await client.exec.templates.generate({ name: 'fetch-items', variables: { url: 'https://api.example.com/items' }, outputPath: 'fetch-items.ts', saveFile: true});Responses
Section titled “Responses”{ "generated": true, "template": "fetch-items", "code": "export default async function() {\n const items = await fetch('https://api.example.com/items').then(r => r.json());\n console.log(items);\n}", "saved": true, "path": "fetch-items.ts", "resolvedPath": "default/1/fetch-items.ts", "variables": { "url": "https://api.example.com/items" }}{ "error": "name 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: “name is required”, “Invalid template name: only letters, digits, dots, hyphens, and underscores (max 64 chars)”, “Missing required variables”, “outputPath is required when saveFile=true”, “A secret directive in the generated script is the redaction placeholder [REDACTED]; send the real value”, “Script path holds a character the router refuses, so no URL could reach it. Use A-Z a-z 0-9 - _ . / (a [param] segment is filled from the URL)”, “Invalid outputPath: missing file name”, “Invalid path: a parent of the path is a file, not a directory”, “outputPath is a directory; give the full path of the file”, “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: REDACTED_SECRET_UNRESOLVED, INVALID_PATH, UNROUTABLE_PATH, NOT_A_DIRECTORY, IS_A_DIRECTORY.
{ "error": "Path is excluded from access", "code": "ERROR_403", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_403 | Forbidden | The service answers 403 with this code; the message names the cause. Messages: “Path is excluded from access”, “Invalid outputPath: must resolve inside the scripts directory”, “Invalid outputPath: resolves outside the scripts directory via symlink”, “Invalid outputPath: target is a symlink”. | None |
{ "error": "Template 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. | None |
{ "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. | None |
{ "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. | None |
{ "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”. | None |
{ "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. Messages: “Scripts directory is not resolvable”, “Internal server error”. Other messages on this status are composed at runtime and are not listed here. | None |
Manage custom templates
Section titled “Manage custom templates”Maintain your own library of templates under _hoody/templates/. Use these endpoints to create, update, and delete custom templates. The full template name pattern is letters, digits, dots, hyphens, and underscores, up to 64 characters, starting with a letter or digit, and never containing ...
POST /api/v1/exec/templates/create-custom
Section titled “POST /api/v1/exec/templates/create-custom”Create a new custom template. The template is stored with a generated metadata header and is available to templates/list, templates/preview, and templates/generate once the call succeeds.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Template name. Must match the pattern ^(?!.*\\.\\.)[A-Za-z0-9][A-Za-z0-9._-]{0,63}$. A name already taken by a built-in template is refused with a 409. |
code | string | Yes | Template source. Stored after a generated metadata header. A non-string is refused with a 400. |
metadata | object | No | Template metadata. An omitted member takes its default; a member of the wrong type, or a metadata value that is not an object, is refused with a 400. |
metadata sub-fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
category | string | No | custom | Category used to group templates. |
tags | array of strings | No | (none) | Tags used to filter templates. |
description | string | No | Custom template | Human-readable description. |
params | array of strings | No | (none) | Names of the variables templates/generate must be given to fill this template. |
version | string | No | 1.0.0 | Template version. |
author | string | No | User | Template author. |
Example request
Section titled “Example request”curl -X POST "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/templates/create-custom" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "my-template", "code": "export default async function({name}: { name: string }) {\n console.log(`Hello, ${name}!`);\n}", "metadata": { "category": "utility", "description": "Greet a user by name.", "params": ["name"], "tags": ["greeting"] } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.exec.templates.create({ name: 'my-template', code: 'export default async function({name}: { name: string }) {\n console.log(`Hello, ${name}!`);\n}', metadata: { category: 'utility', description: 'Greet a user by name.', params: ['name'], tags: ['greeting'] }});Responses
Section titled “Responses”{ "created": true, "name": "my-template", "path": "default/my-template.ts", "metadata": { "name": "my-template", "category": "utility", "tags": ["greeting"], "description": "Greet a user by name.", "params": ["name"], "version": "1.0.0", "author": "User" }}{ "error": "name and code are 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: “name and code are required”, “code must be a string”, “metadata must be an object”, “Invalid template name: only letters, digits, dots, hyphens, and underscores (max 64 chars)”, “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. | None |
{ "error": "Custom templates directory resolves outside the scripts directory", "code": "ERROR_403", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_403 | Forbidden | The service answers 403 with this code; the message names the cause. | None |
{ "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. | None |
{ "error": "Cannot create a custom template that shadows a built-in", "code": "ERROR_409", "timestamp": "2026-01-01T00:00:00.000Z"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ERROR_409 | Conflict | The service answers 409 with this code; the message names the cause. Other messages on this status are composed at runtime and are not listed here. | None |
{ "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. | None |
{ "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”. | None |
{ "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. | None |
PUT /api/v1/exec/templates/update-custom/{name}
Section titled “PUT /api/v1/exec/templates/update-custom/{name}”Update an existing custom template’s source and/or metadata. The new metadata is merged with the existing one.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | Template name. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
code | string | No | New template source. Replaces the existing code. |
metadata | object | No | Metadata fields to update. Merged with the existing metadata. |
Example request
Section titled “Example request”curl -X PUT "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/templates/update-custom/my-template" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "code": "export default async function({name}: { name: string }) {\n console.log(`Hello, ${name}! Updated.`);\n}", "metadata": { "description": "Greet a user by name (updated)." } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.exec.templates.update('my-template', { code: 'export default async function({name}: { name: string }) {\n console.log(`Hello, ${name}! Updated.`);\n}', metadata: { description: 'Greet a user by name (updated).' }});Responses
Section titled “Responses”{ "updated": true, "name": "my-template", "metadata": { "name": "my-template", "category": "utility", "tags": ["greeting"], "description": "Greet a user by name (updated).", "params": ["name"], "version": "1.0.0", "author": "User" }}{ "error": "name 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: “name is required”, “Invalid template name: only letters, digits, dots, hyphens, and underscores (max 64 chars)”, “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. | None |
{ "error": "Template 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. | None |
{ "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. | None |
{ "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. | None |
{ "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”. | None |
{ "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. | None |
DELETE /api/v1/exec/templates/delete-custom/{name}
Section titled “DELETE /api/v1/exec/templates/delete-custom/{name}”Delete a custom template. The template file is removed from _hoody/templates/.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | Template name. |
Example request
Section titled “Example request”curl -X DELETE "https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/v1/exec/templates/delete-custom/my-template" \ -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.templates.delete('my-template');Responses
Section titled “Responses”{ "deleted": true, "name": "my-template"}{ "error": "name 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: “name is required”, “Invalid template name: only letters, digits, dots, hyphens, and underscores (max 64 chars)”, “Malformed query string” (Percent-encoding failed to decode. Use valid %XX escapes.). | None |
{ "error": "Template 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. | None |
{ "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”. | None |
{ "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. | None |