Cron-as-a-service. Hoody Cron wraps the system crontab in a REST API with managed entries, enable/disable toggles, auto-expiration, and per-user isolation. Standard 5-field cron expressions plus macros like @hourly and @daily.
What You Can Do
Section titled “What You Can Do”- Managed Entries - Create, update, delete cron jobs via JSON API with UUIDs
- Enable/Disable - Toggle jobs on and off without deleting them
- Auto-Expiration - Set
expires_atfor temporary jobs that clean themselves up - Per-User Isolation - Each system user has their own crontab
- Raw Crontab - Read and write the full crontab file directly
- Standard Cron - 5-field expressions (
* * * * *) plus macros (@hourly,@daily,@weekly,@monthly,@yearly) - Comments & Metadata - Attach human-readable comments to managed entries
API Endpoints Summary
Section titled “API Endpoints Summary”All endpoints accessed relative to your Cron service URL:
https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.comManaged Entries:
GET /users/{user}/entries- List managed entries for a userPOST /users/{user}/entries- Create a new managed entryGET /users/{user}/entries/{id}- Get a specific entryPATCH /users/{user}/entries/{id}- Update an entryDELETE /users/{user}/entries/{id}- Delete an entry
Raw Crontab:
GET /crontab- List all user crontabsGET /users/{user}/crontab- Get raw crontab for a userPUT /users/{user}/crontab- Replace raw crontab for a user
System:
GET /health- Health check
Quick Start: Create a Scheduled Job
Section titled “Quick Start: Create a Scheduled Job”# Create a cron job that runs daily at 9 AMhoody cron entries create root \ --schedule "0 9 * * *" \ --command "/usr/local/bin/backup.sh" \ --comment "Daily backup at 9 AM" \ -c <container-id>
# List all cron entrieshoody cron entries list root -c <container-id>
# Update a job's schedulehoody cron entries update root $ENTRY_ID \ --schedule "0 12 * * *" \ -c <container-id>
# View the raw crontabhoody cron crontabs get root -c <container-id>import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });
// Create a daily cron jobconst entry = await containerClient.cron.entries.create('root', { schedule: '0 9 * * *', command: '/usr/local/bin/backup.sh', comment: 'Daily backup at 9 AM', enabled: true,});
// List all entriesconst entries = await containerClient.cron.entries.list('root');
// Disable temporarilyawait containerClient.cron.entries.update('root', entry.data.id, { enabled: false,});# Create a daily cron jobcurl -X POST "https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries" \ -H "Content-Type: application/json" \ -d '{ "schedule": "0 9 * * *", "command": "/usr/local/bin/backup.sh", "comment": "Daily backup at 9 AM", "enabled": true }'
# List all entriescurl "https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries"
# Disable a jobcurl -X PATCH "https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries/$ENTRY_ID" \ -H "Content-Type: application/json" \ -d '{"enabled": false}'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Creates the daily backup entry, lists all entries for root, and disables one entry by id. The three links are independent requests, not a sequence — swap in a real entry id for Disable.
# Create
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries&method=POST&json={"schedule":"0%209%20*%20*%20*","command":"/usr/local/bin/backup.sh","comment":"Daily%20backup%20at%209%20AM","enabled":true}&response=transparent
# List
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries&method=GET&response=transparent
# Disable
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries/ENTRY_ID&method=PATCH&json={"enabled":false}&response=transparent Cron Schedule Reference
Section titled “Cron Schedule Reference”Standard 5-field cron expressions:
┌───────────── minute (0-59)│ ┌───────────── hour (0-23)│ │ ┌───────────── day of month (1-31)│ │ │ ┌───────────── month (1-12)│ │ │ │ ┌───────────── day of week (0-7, Sun is 0 or 7)│ │ │ │ │* * * * *Common patterns:
| Schedule | Expression |
|---|---|
| Every minute | * * * * * |
| Every hour | 0 * * * * |
| Every day at midnight | 0 0 * * * |
| Weekdays at 9 AM | 0 9 * * 1-5 |
| Every Sunday at 3 AM | 0 3 * * 0 |
| First day of month | 0 0 1 * * |
Macros: @yearly, @monthly, @weekly, @daily, @hourly
Auto-Expiration
Section titled “Auto-Expiration”Set expires_at on managed entries for temporary jobs that automatically remove themselves:
hoody cron entries create root \ --schedule "*/5 * * * *" \ --command "curl -s http://localhost:8080/health >> /var/log/health.log" \ --comment "Temporary health monitoring, auto-expires" \ --expires-at "2026-12-31T00:00:00Z" \ -c <container-id>import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });
const entry = await containerClient.cron.entries.create('root', { schedule: '*/5 * * * *', command: 'curl -s http://localhost:8080/health >> /var/log/health.log', comment: 'Temporary health monitoring, auto-expires', enabled: true, expires_at: '2026-12-31T00:00:00Z',});curl -X POST "https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries" \ -H "Content-Type: application/json" \ -d '{ "schedule": "*/5 * * * *", "command": "curl -s http://localhost:8080/health >> /var/log/health.log", "comment": "Temporary health monitoring, auto-expires", "enabled": true, "expires_at": "2026-12-31T00:00:00Z" }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Creates a health-check entry that runs every five minutes and stops being scheduled after the given timestamp.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/entries&method=POST&json={"schedule":"*/5%20*%20*%20*%20*","command":"curl%20-s%20http://localhost:8080/health%20>>%20/var/log/health.log","comment":"Temporary%20health%20monitoring,%20auto-expires","enabled":true,"expires_at":"2026-12-31T00:00:00Z"}&response=transparent expires_at is RFC3339 and must be in the future: the API rejects a timestamp that has already passed.
Raw Crontab Access
Section titled “Raw Crontab Access”For full control, read and write the raw crontab directly:
Read the current crontab, then replace it wholesale:
# Readhoody cron crontabs get root -c <container-id>
# Replacehoody cron crontabs replace root \ --crontab $'# Custom crontab\n0 * * * * /usr/local/bin/hourly-task.sh\n0 0 * * * /usr/local/bin/daily-task.sh' \ -c <container-id>import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });
// Readconst crontab = await containerClient.cron.crontab.get('root');
// Replaceawait containerClient.cron.crontab.put('root', { crontab: '# Custom crontab\n0 * * * * /usr/local/bin/hourly-task.sh\n0 0 * * * /usr/local/bin/daily-task.sh',});# Readcurl "https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/crontab"
# Replacecurl -X PUT "https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/crontab" \ -H "Content-Type: application/json" \ -d '{ "crontab": "# Custom crontab\n0 * * * * /usr/local/bin/hourly-task.sh\n0 0 * * * /usr/local/bin/daily-task.sh" }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Reads the current crontab for root, then replaces it wholesale with the two-line schedule shown. Replace overwrites the entire crontab, not just one entry.
# Read
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/crontab&method=GET&response=transparent
# Replace
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-cron-1.SERVER.containers.hoody.com/users/root/crontab&method=PUT&json={"crontab":"%23%20Custom%20crontab\n0%20*%20*%20*%20*%20/usr/local/bin/hourly-task.sh\n0%200%20*%20*%20*%20/usr/local/bin/daily-task.sh"}&response=transparent Use Cases
Section titled “Use Cases”- Scheduled backups - Run backup scripts at regular intervals
- Data processing - ETL jobs, report generation, log rotation
- Health monitoring - Periodic health checks with auto-expiring entries
- Temporary tasks - Time-limited monitoring or data collection
- Maintenance - Cache cleanup, database optimization, certificate renewal
What’s Next
Section titled “What’s Next”- Cron API Reference - Complete API documentation
- Managed Entries API - CRUD operations for managed entries
- Raw Crontab API - Direct crontab file access
- Daemons - For always-running processes (vs scheduled tasks)
- Exec - Execute scripts as HTTP endpoints