Managed entries
Section titled “Managed entries”The Managed Entries API manages cron entries owned by a specific system user inside a Hoody container. Managed entries are persisted alongside the raw crontab, identified by a stable UUID, and can be enabled, disabled, rescheduled, or given an expiration timestamp.
All endpoints on this page are scoped to a single container, addressed through the cron host pattern `{projectId}-{containerId}-cron-1.{server}.containers.hoody.com`. Replace {projectId} and {containerId} with the IDs of the project and container that own the cron service, and replace {server} with the region node that hosts the container.
The list endpoint returns both managed entries (those created through this API) and raw entries (any non-managed lines that already exist in the user’s crontab).
List entries
Section titled “List entries”Returns a paginated list of crontab entries belonging to a system user. Each item in the entries array is either a managed entry or a raw line.
GET /api/v1/cron/users/{user}/entries
Section titled “GET /api/v1/cron/users/{user}/entries”Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
user | path | string | Yes | System username |
page | query | integer | No | Page number (1-based, default 1) |
limit | query | integer | No | Items per page (default 50, max 200) |
curl -X GET "https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com/api/v1/cron/users/www-data/entries?page=1&limit=50"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const entry of client.cron.entries.listIterator('www-data', { page: 1, limit: 50 })) { console.log(entry);}{ "user": "www-data", "entries": [ { "id": "9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f", "name": "Nightly backup", "schedule": "0 2 * * *", "schedule_human": "At 02:00 AM every day", "command": "/opt/backups/run.sh", "comment": "Nightly database snapshot", "created_at": "2025-03-12T08:00:00Z", "updated_at": "2025-03-12T08:00:00Z", "expires_at": null, "expired": false, "enabled": true, "type": "managed" }, { "line": "*/15 * * * * /usr/local/bin/healthcheck.sh", "type": "raw" } ], "page": 1, "limit": 50, "total": 2}{ "code": "INVALID_PAGINATION", "message": "Limit must be between 1 and 200", "details": "limit=\"-1\" is not a whole number"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_USER | Invalid user | User parameter failed validation | Provide a valid system username |
INVALID_PAGINATION | Invalid pagination | Page or limit is not a whole number or is out of range | Use page >= 1 and limit between 1 and 200 |
{ "code": "USER_NOT_FOUND", "message": "User not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
USER_NOT_FOUND | User not found | The requested system user does not exist | Create the user or choose an existing username |
{ "code": "STORED_CRONTAB_INVALID", "message": "The crontab stored for user root cannot be read; replace it with PUT /users/root/crontab to repair it", "details": "line 3: contains U+2028 (a Unicode line break), which a crontab may not contain"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
STORED_CRONTAB_INVALID | Stored crontab invalid | The crontab already stored for the user cannot be used, so the request was not carried out although nothing in it is wrong: the stored crontab holds a Unicode line break, a # hoody-cron: line with two id fields, or more than the parser cap of 10000 lines or 40960000 bytes, or crontab(1) refuses its lines when they are written back (without expired entries, without a deleted entry, or with a new or updated entry next to an invalid raw line); details says which | Replace the whole crontab with PUT /api/v1/cron/users/{user}/crontab, which does not read the stored one |
{ "code": "BACKEND_ERROR", "message": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_ERROR | Backend error | Failed to read or write system crontab | Check crontab availability and permissions |
Create entry
Section titled “Create entry”Creates a new managed cron entry for the given system user. The required schedule is a standard 5-field cron expression (or @daily style macro), and command is the shell command to execute. Optional fields include a human-readable name, an inline comment, the initial enabled state, and an expires_at timestamp.
If a managed entry already exists with the same schedule, command, name, comment, expires_at, and enabled, the request returns the existing entry with status 200 instead of writing a duplicate. If the entry exists with a different name, comment, expires_at, or enabled, the request returns 409 ENTRY_EXISTS.
POST /api/v1/cron/users/{user}/entries
Section titled “POST /api/v1/cron/users/{user}/entries”Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
user | path | string | Yes | System username |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
schedule | string | Yes | Cron expression (5 fields or @daily style macro) |
command | string | Yes | Shell command to execute |
name | string | null | No | Human-readable label for the entry |
comment | string | null | No | Inline comment placed before the schedule |
enabled | boolean | null | No | Initial enabled state |
expires_at | string | null | No | RFC 3339 timestamp strictly in the future (a past value is refused with 400 EXPIRES_IN_PAST); the entry is removed when reached |
curl -X POST "https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com/api/v1/cron/users/www-data/entries" \ -H "Content-Type: application/json" \ -d '{ "schedule": "0 2 * * *", "command": "/opt/backups/run.sh", "name": "Nightly backup", "comment": "Nightly database snapshot", "enabled": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const entry = await client.cron.entries.create('www-data', { schedule: '0 2 * * *', command: '/opt/backups/run.sh', name: 'Nightly backup', comment: 'Nightly database snapshot', enabled: true,});console.log(entry.data.id);{ "id": "9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f", "user": "www-data", "name": "Nightly backup", "schedule": "0 2 * * *", "schedule_human": "At 02:00 AM every day", "command": "/opt/backups/run.sh", "comment": "Nightly database snapshot", "created_at": "2025-03-12T08:00:00Z", "updated_at": "2025-03-12T08:00:00Z", "expires_at": null, "expired": false, "enabled": true}{ "id": "9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f", "user": "www-data", "name": "Nightly backup", "schedule": "0 2 * * *", "schedule_human": "At 02:00 AM every day", "command": "/opt/backups/run.sh", "comment": "Nightly database snapshot", "created_at": "2025-03-12T08:00:00Z", "updated_at": "2025-03-12T08:00:00Z", "expires_at": null, "expired": false, "enabled": true}{ "code": "INVALID_USER", "message": "Invalid user"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_USER | Invalid user | User parameter failed validation | Provide a valid system username |
INVALID_SCHEDULE | Invalid schedule | Schedule is not a valid cron expression; details names the field and the reason | Use a standard 5-field cron expression or @daily style macros |
INVALID_COMMAND | Invalid command | Command is empty, longer than 4096 bytes, or contains a line break; details says which. Also returned when a crontab request body holds a Unicode line break | Provide a single-line command of at most 4096 bytes |
INVALID_NAME | Invalid name | Name is empty, longer than 120 bytes, or contains a line break; details says which | Provide a single-line name of at most 120 bytes |
INVALID_COMMENT | Invalid comment | Comment is empty, longer than 500 bytes, or contains a line break; details says which | Provide a single-line comment of at most 500 bytes |
INVALID_EXPIRES_AT | Invalid expires_at | expires_at is not an RFC 3339 timestamp | Send expires_at as an RFC 3339 timestamp such as 2030-01-01T00:00:00Z |
EXPIRES_IN_PAST | expires_at in the past | expires_at is not later than the current time | Send an expires_at in the future, or clear_expiration on update |
INVALID_CRONTAB | Invalid crontab | A line of the crontab body is not valid crontab syntax, or crontab(1) refused the crontab body or the crontab with the created or updated entry; details names the line and the reason. The body or the entry is not written | Fix the named line; use 5 time fields (or an @ macro) and a command, an environment assignment, or a # comment |
INVALID_JSON | Invalid JSON | Request body is not valid JSON; details gives the position | Send a well-formed JSON object |
INVALID_BODY | Invalid body | Request body is valid JSON but a required field is missing or a field has the wrong type; details names it. Also returned when the body could not be read to its end | Send the fields of the request schema with their documented types |
{ "code": "USER_NOT_FOUND", "message": "User not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
USER_NOT_FOUND | User not found | The requested system user does not exist | Create the user or choose an existing username |
{ "code": "STORED_CRONTAB_INVALID", "message": "The crontab stored for user root cannot be read; replace it with PUT /users/root/crontab to repair it", "details": "line 3: contains U+2028 (a Unicode line break), which a crontab may not contain"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
STORED_CRONTAB_INVALID | Stored crontab invalid | The crontab already stored for the user cannot be used, so the request was not carried out although nothing in it is wrong: the stored crontab holds a Unicode line break, a # hoody-cron: line with two id fields, or more than the parser cap of 10000 lines or 40960000 bytes, or crontab(1) refuses its lines when they are written back (without expired entries, without a deleted entry, or with a new or updated entry next to an invalid raw line); details says which | Replace the whole crontab with PUT /api/v1/cron/users/{user}/crontab, which does not read the stored one |
ENTRY_EXISTS | Entry exists | The user already has a managed entry with this schedule and command, with a different name, comment, expires_at or enabled; details gives its id. Sending the same request again answers 200 with that entry instead | Update the existing entry with PATCH, or delete it first |
{ "code": "PAYLOAD_TOO_LARGE", "message": "Request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | Request body is larger than --max-crontab-bytes, or a crontab request body exceeds the parser cap of 10000 lines or 40960000 bytes | Send a smaller body; shorten the crontab |
{ "code": "UNSUPPORTED_MEDIA_TYPE", "message": "Content-Type must be application/json or application/*+json"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UNSUPPORTED_MEDIA_TYPE | Unsupported media type | Request has no Content-Type header, or one that is neither application/json nor application/*+json (parameters allowed) | Send the body with Content-Type: application/json |
{ "code": "BACKEND_ERROR", "message": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_ERROR | Backend error | Failed to read or write system crontab | Check crontab availability and permissions |
Get entry
Section titled “Get entry”Returns a single managed entry by id. Raw entries are not addressable individually and return ENTRY_NOT_FOUND if requested.
GET /api/v1/cron/users/{user}/entries/{id}
Section titled “GET /api/v1/cron/users/{user}/entries/{id}”Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
user | path | string | Yes | System username |
id | path | string | Yes | Managed entry id (UUID) |
curl -X GET "https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com/api/v1/cron/users/www-data/entries/9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const entry = await client.cron.entries.get('www-data', '9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f');console.log(entry.data.schedule, entry.data.command);{ "id": "9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f", "user": "www-data", "name": "Nightly backup", "schedule": "0 2 * * *", "schedule_human": "At 02:00 AM every day", "command": "/opt/backups/run.sh", "comment": "Nightly database snapshot", "created_at": "2025-03-12T08:00:00Z", "updated_at": "2025-03-12T08:00:00Z", "expires_at": null, "expired": false, "enabled": true}{ "code": "INVALID_USER", "message": "Invalid user"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_USER | Invalid user | User parameter failed validation | Provide a valid system username |
INVALID_ID | Invalid id | Entry id in the path is not a UUID, a # hoody-cron: line of a crontab request body holds two id fields, or two managed entries of a crontab body share an id | Use the id returned when the entry was created or listed; give each managed entry of a crontab body its own id |
{ "code": "USER_NOT_FOUND", "message": "User not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
USER_NOT_FOUND | User not found | The requested system user does not exist | Create the user or choose an existing username |
ENTRY_NOT_FOUND | Entry not found | No managed entry with the provided id exists | List entries and retry with a valid id |
{ "code": "STORED_CRONTAB_INVALID", "message": "The crontab stored for user root cannot be read; replace it with PUT /users/root/crontab to repair it", "details": "line 3: contains U+2028 (a Unicode line break), which a crontab may not contain"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
STORED_CRONTAB_INVALID | Stored crontab invalid | The crontab already stored for the user cannot be used, so the request was not carried out although nothing in it is wrong: the stored crontab holds a Unicode line break, a # hoody-cron: line with two id fields, or more than the parser cap of 10000 lines or 40960000 bytes, or crontab(1) refuses its lines when they are written back (without expired entries, without a deleted entry, or with a new or updated entry next to an invalid raw line); details says which | Replace the whole crontab with PUT /api/v1/cron/users/{user}/crontab, which does not read the stored one |
{ "code": "BACKEND_ERROR", "message": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_ERROR | Backend error | Failed to read or write system crontab | Check crontab availability and permissions |
Update entry
Section titled “Update entry”Partially updates a managed entry. Any field omitted from the request body is left unchanged. Pass clear_expiration: true to remove an existing expires_at instead of replacing it.
PATCH /api/v1/cron/users/{user}/entries/{id}
Section titled “PATCH /api/v1/cron/users/{user}/entries/{id}”Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
user | path | string | Yes | System username |
id | path | string | Yes | Managed entry id (UUID) |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
schedule | string | null | No | New cron expression |
command | string | null | No | New shell command |
name | string | null | No | New human-readable label |
comment | string | null | No | New inline comment |
enabled | boolean | null | No | New enabled state |
expires_at | string | null | No | New RFC 3339 expiration timestamp, strictly in the future (a past value is refused with 400 EXPIRES_IN_PAST); the entry is removed when reached |
clear_expiration | boolean | null | No | When true, clears any existing expires_at |
curl -X PATCH "https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com/api/v1/cron/users/www-data/entries/9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f" \ -H "Content-Type: application/json" \ -d '{ "schedule": "30 2 * * *", "enabled": false }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const entry = await client.cron.entries.update('www-data', '9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f', { schedule: '30 2 * * *', enabled: false,});console.log(entry.data.enabled);{ "id": "9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f", "user": "www-data", "name": "Nightly backup", "schedule": "30 2 * * *", "schedule_human": "At 02:30 AM every day", "command": "/opt/backups/run.sh", "comment": "Nightly database snapshot", "created_at": "2025-03-12T08:00:00Z", "updated_at": "2025-03-12T08:15:42Z", "expires_at": null, "expired": false, "enabled": false}{ "code": "INVALID_USER", "message": "Invalid user"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_USER | Invalid user | User parameter failed validation | Provide a valid system username |
INVALID_SCHEDULE | Invalid schedule | Schedule is not a valid cron expression; details names the field and the reason | Use a standard 5-field cron expression or @daily style macros |
INVALID_COMMAND | Invalid command | Command is empty, longer than 4096 bytes, or contains a line break; details says which. Also returned when a crontab request body holds a Unicode line break | Provide a single-line command of at most 4096 bytes |
INVALID_NAME | Invalid name | Name is empty, longer than 120 bytes, or contains a line break; details says which | Provide a single-line name of at most 120 bytes |
INVALID_COMMENT | Invalid comment | Comment is empty, longer than 500 bytes, or contains a line break; details says which | Provide a single-line comment of at most 500 bytes |
INVALID_EXPIRES_AT | Invalid expires_at | expires_at is not an RFC 3339 timestamp | Send expires_at as an RFC 3339 timestamp such as 2030-01-01T00:00:00Z |
EXPIRES_IN_PAST | expires_at in the past | expires_at is not later than the current time | Send an expires_at in the future, or clear_expiration on update |
INVALID_ID | Invalid id | Entry id in the path is not a UUID, a # hoody-cron: line of a crontab request body holds two id fields, or two managed entries of a crontab body share an id | Use the id returned when the entry was created or listed; give each managed entry of a crontab body its own id |
INVALID_CRONTAB | Invalid crontab | A line of the crontab body is not valid crontab syntax, or crontab(1) refused the crontab body or the crontab with the created or updated entry; details names the line and the reason. The body or the entry is not written | Fix the named line; use 5 time fields (or an @ macro) and a command, an environment assignment, or a # comment |
INVALID_JSON | Invalid JSON | Request body is not valid JSON; details gives the position | Send a well-formed JSON object |
INVALID_BODY | Invalid body | Request body is valid JSON but a required field is missing or a field has the wrong type; details names it. Also returned when the body could not be read to its end | Send the fields of the request schema with their documented types |
{ "code": "USER_NOT_FOUND", "message": "User not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
USER_NOT_FOUND | User not found | The requested system user does not exist | Create the user or choose an existing username |
ENTRY_NOT_FOUND | Entry not found | No managed entry with the provided id exists | List entries and retry with a valid id |
{ "code": "STORED_CRONTAB_INVALID", "message": "The crontab stored for user root cannot be read; replace it with PUT /users/root/crontab to repair it", "details": "line 3: contains U+2028 (a Unicode line break), which a crontab may not contain"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
STORED_CRONTAB_INVALID | Stored crontab invalid | The crontab already stored for the user cannot be used, so the request was not carried out although nothing in it is wrong: the stored crontab holds a Unicode line break, a # hoody-cron: line with two id fields, or more than the parser cap of 10000 lines or 40960000 bytes, or crontab(1) refuses its lines when they are written back (without expired entries, without a deleted entry, or with a new or updated entry next to an invalid raw line); details says which | Replace the whole crontab with PUT /api/v1/cron/users/{user}/crontab, which does not read the stored one |
{ "code": "PAYLOAD_TOO_LARGE", "message": "Request body exceeds the configured size limit"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Payload too large | Request body is larger than --max-crontab-bytes, or a crontab request body exceeds the parser cap of 10000 lines or 40960000 bytes | Send a smaller body; shorten the crontab |
{ "code": "UNSUPPORTED_MEDIA_TYPE", "message": "Content-Type must be application/json or application/*+json"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UNSUPPORTED_MEDIA_TYPE | Unsupported media type | Request has no Content-Type header, or one that is neither application/json nor application/*+json (parameters allowed) | Send the body with Content-Type: application/json |
{ "code": "BACKEND_ERROR", "message": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_ERROR | Backend error | Failed to read or write system crontab | Check crontab availability and permissions |
Delete entry
Section titled “Delete entry”Removes a managed entry from the user’s crontab. The entry is identified by its UUID; raw lines cannot be deleted through this endpoint.
DELETE /api/v1/cron/users/{user}/entries/{id}
Section titled “DELETE /api/v1/cron/users/{user}/entries/{id}”Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
user | path | string | Yes | System username |
id | path | string | Yes | Managed entry id (UUID) |
curl -X DELETE "https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com/api/v1/cron/users/www-data/entries/9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-cron-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.cron.entries.delete('www-data', '9c1b9c2a-7e8d-4f3a-b5e1-2a3b4c5d6e7f');console.log(result.data.deleted);{ "deleted": true}{ "code": "INVALID_USER", "message": "Invalid user"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_USER | Invalid user | User parameter failed validation | Provide a valid system username |
INVALID_ID | Invalid id | Entry id in the path is not a UUID, a # hoody-cron: line of a crontab request body holds two id fields, or two managed entries of a crontab body share an id | Use the id returned when the entry was created or listed; give each managed entry of a crontab body its own id |
{ "code": "USER_NOT_FOUND", "message": "User not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
USER_NOT_FOUND | User not found | The requested system user does not exist | Create the user or choose an existing username |
ENTRY_NOT_FOUND | Entry not found | No managed entry with the provided id exists | List entries and retry with a valid id |
{ "code": "STORED_CRONTAB_INVALID", "message": "The crontab stored for user root cannot be read; replace it with PUT /users/root/crontab to repair it", "details": "line 3: contains U+2028 (a Unicode line break), which a crontab may not contain"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
STORED_CRONTAB_INVALID | Stored crontab invalid | The crontab already stored for the user cannot be used, so the request was not carried out although nothing in it is wrong: the stored crontab holds a Unicode line break, a # hoody-cron: line with two id fields, or more than the parser cap of 10000 lines or 40960000 bytes, or crontab(1) refuses its lines when they are written back (without expired entries, without a deleted entry, or with a new or updated entry next to an invalid raw line); details says which | Replace the whole crontab with PUT /api/v1/cron/users/{user}/crontab, which does not read the stored one |
{ "code": "BACKEND_ERROR", "message": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
BACKEND_ERROR | Backend error | Failed to read or write system crontab | Check crontab availability and permissions |