Your First API
Section titled “Your First API”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.
Step 1: Write a Script
Section titled “Step 1: Write a Script”# 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 serverlessconst name = metadata.query.name || "World";return { message: `Hello, ${name}!`, timestamp: new Date().toISOString() };'// Write the script via the exec service (not the Files service)const execUrl = `https://${PROJECT_ID}-${CONTAINER_ID}-exec-1.${SERVER}.containers.hoody.icu`;
const script = `// @mode serverlessconst name = metadata.query.name || "World";return { message: \`Hello, \${name}!\`, timestamp: new Date().toISOString() };`;
await fetch(`${execUrl}/api/v1/exec/scripts/write`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: 'api/hello.js', content: script, createDirs: true, validate: true })});# Write the script via the exec service's scripts/write endpoint.# `path` is relative to the exec scripts directory.curl -X POST "https://$PROJECT-$CONTAINER-exec-1.$NODE.containers.hoody.icu/api/v1/exec/scripts/write" \ -H "Content-Type: application/json" \ -d '{ "path": "api/hello.js", "content": "// @mode serverless\nconst name = metadata.query.name || \"World\";\nreturn { message: `Hello, ${name}!`, timestamp: new Date().toISOString() };", "createDirs": true, "validate": true }'Step 2: Call It
Section titled “Step 2: Call It”Your script is already live at:
https://{projectId}-{containerId}-exec-1.{serverName}.containers.hoody.icu/api/hello# Call your new API endpoint directly via its URLcurl "https://$PROJECT_ID-$CONTAINER_ID-exec-1.$SERVER.containers.hoody.icu/api/hello?name=Developer"// Exec scripts are live HTTP endpoints — call them directlyconst response = await fetch( `https://${PROJECT_ID}-${CONTAINER_ID}-exec-1.${SERVER}.containers.hoody.icu/api/hello?name=Developer`);const data = await response.json();console.log(data);// { message: "Hello, Developer!", timestamp: "2026-03-04T..." }curl "https://$PROJECT-$CONTAINER-exec-1.$SERVER.containers.hoody.icu/api/hello?name=Developer"Response:
{ "message": "Hello, Developer!", "timestamp": "2026-03-04T12:00:00.000Z" }That URL works from anywhere: a browser, a webhook, an AI agent, a phone. No deployment step.
Step 3: Chain Services
Section titled “Step 3: Chain Services”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:
// @mode serverlessconst { 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 databaseawait 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.
What Just Happened?
Section titled “What Just Happened?”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 Path | URL 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 →