Skip to content
Hoody.com

With hoody-exec, any script in its scripts directory is a live HTTP endpoint: write the file, and the URL exists. You do not need Express, Docker, or a CI/CD pipeline. Always write exec scripts through the exec service’s own scripts/write endpoint (not the Files service) so they land in the exec-managed scripts directory and pass validation.


Terminal window
# Write a script to the exec scripts directory (path is relative to that dir)
# All kit commands require a target container: -c CONTAINER_ID (or HOODY_CONTAINER)
hoody exec scripts write -c CONTAINER_ID --path "api/hello.js" --create-dirs --content \
'// @mode serverless
const name = metadata.query.name || "World";
return { message: `Hello, ${name}!`, timestamp: new Date().toISOString() };'

Your script is already live at:

https://{projectId}-{containerId}-exec-1.{server}.containers.hoody.com/api/hello
Terminal window
# Call your new API endpoint directly via its URL
curl "https://$PROJECT_ID-$CONTAINER_ID-exec-1.$SERVER.containers.hoody.com/api/hello?name=Developer"

That URL works from anywhere: a browser, a webhook, an AI agent, a phone. There is no separate deployment step.


Exec scripts can call any other service in the container. Sibling services share the same host as your script; only the service slug changes. Derive the base host from metadata.url (the full request URL) and swap in the target service:

scripts/default/1/api/deploy-report.ts
// @mode serverless
// metadata.url is the full request URL, e.g.
// https://PROJECT_ID-CONTAINER_ID-exec-1.SERVER.containers.hoody.com/api/deploy-report
const { hostname } = new URL(metadata.url);
const host = (svc, idx = 1) =>
`https://${hostname.replace(/-exec-\d+\./, `-${svc}-${idx}.`)}`;
// Run the build (ephemeral=true auto-creates an isolated PTY for programmatic execution)
const build = await fetch(`${host('terminal')}/api/v1/terminal/execute?ephemeral=true`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command: 'npm run build', wait: true })
});
// Log to database
await fetch(`${host('sqlite')}/api/v1/sqlite/db?db=logs&create_db_if_missing=true`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
transaction: [
{ statement: `INSERT INTO deploys (status, time) VALUES ('success', '${new Date().toISOString()}')` }
]
})
});
// Send a notification: `display` is required, and `1` is the container's primary desktop
await fetch(`${host('n')}/api/v1/notifications/notify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ display: '1', summary: 'Deploy complete', body: 'Build succeeded' })
});
return { status: 'deployed', timestamp: new Date().toISOString() };

One script coordinates three services with plain HTTP requests and no additional infrastructure.


You created a live API endpoint by writing a file, with no package.json, npm install, docker build, or deploy command. The script’s path, relative to the exec scripts directory, is the URL path:

Script pathURL path
api/hello.js/api/hello
api/users/list.js/api/users/list
api/deploy.ts/api/deploy
webhooks/stripe.js/webhooks/stripe

Each script is an endpoint, each endpoint can call any service in the container, and every service speaks HTTP. Writing the script, calling it, and chaining services all happened through URLs.

Next: The Hoody Kit →