Skip to content

Write a script. It’s already an API.

With hoody-exec, any script you write to its scripts directory becomes a live HTTP endpoint — instantly. No Express. No Docker. No CI/CD. You write the file, and the URL exists. 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.

This is what “scripts become APIs” actually means.


Terminal window
# Write a script to the exec scripts directory (path is relative to that dir)
hoody exec scripts write --path "api/hello.js" --create-dirs --validate --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.{serverName}.containers.hoody.icu/api/hello
Terminal window
# Call your new API endpoint directly via its URL
curl "https://$PROJECT_ID-$CONTAINER_ID-exec-1.$SERVER.containers.hoody.icu/api/hello?name=Developer"

That URL works from anywhere: a browser, a webhook, an AI agent, a phone. No deployment step.


The real power is composition. Your exec scripts can call any other service in the container. Build the sibling service URLs from the same project/container identifiers you already have — exec’s metadata carries projectId, containerId, nodeId (the node-… server name), and domain, so compose the host explicitly:

/scripts/api/deploy-report.js
// @mode serverless
const { projectId, containerId, nodeId, domain } = metadata;
const host = (svc, idx = 1) =>
`https://${projectId}-${containerId}-${svc}-${idx}.${nodeId}.${domain}`;
// 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 notification — `display` selects the target display ID ("0" = default)
await fetch(`${host('n')}/api/v1/notifications/notify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ display: '0', summary: 'Deploy complete', body: 'Build succeeded' })
});
return { status: 'deployed', timestamp: new Date().toISOString() };

One script. Three services. Zero infrastructure. That’s what HTTP composability looks like.


You created a live API endpoint by writing a file. No package.json, no npm install, no docker build, no 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.js/api/deploy
webhooks/stripe.js/webhooks/stripe

This is the HTTP revolution in practice. Every script is an endpoint. Every endpoint is composable. Every service speaks HTTP. The platform disappears — what remains is just URLs.

Next: The Hoody Kit →