Skip to content

Scheduled Jobs

The Scheduled Jobs API lets you create recurring tasks that execute agent skills on a cron-based schedule. Under the hood, Horizon uses BullMQ as the job queue, which provides reliable scheduling, automatic retries, and concurrency management on top of Redis. Scheduled jobs are persisted in Supabase and synchronized with BullMQ repeatable jobs.


Scheduled jobs use standard five-field cron expressions:

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, where 0 and 7 are Sunday)
│ │ │ │ │
* * * * *
Expression Description
0 9 * * 1-5 Every weekday at 9:00 AM
*/15 * * * * Every 15 minutes
0 0 1 * * First day of each month at midnight
30 17 * * 5 Every Friday at 5:30 PM
0 */6 * * * Every 6 hours

All cron expressions are evaluated in UTC. BullMQ calculates the next execution time from the cron expression and enqueues the job accordingly.


POST/api/scheduled-jobs

Create a new scheduled job that executes a skill on the specified cron schedule.

Requires authentication via x-api-key header.

Request Body

ParameterTypeDescription
agent_id requiredstringThe agent that will execute the skill.
skill_category requiredstringThe skill category (e.g., 'quickbooks', 'sage-intacct', 'web').
skill_name requiredstringThe skill name in kebab-case (e.g., 'profit-and-loss-report').
skill_versionstringThe skill version. Default: 'v1.0'.
cron_expression requiredstringA standard five-field cron expression (evaluated in UTC).
inputobjectOptional input parameters passed to the skill on each execution.
enabledbooleanWhether the job is active. Default: true.
curl
curl -X POST https://api.horizonplatform.ai/api/scheduled-jobs \
-H "x-api-key: hz_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_001",
"skill_category": "quickbooks",
"skill_name": "profit-and-loss-report",
"skill_version": "v1.0",
"cron_expression": "0 9 * * 1-5",
"input": {
"accounting_method": "Accrual",
"date_range": "last_7_days"
},
"enabled": true
}'
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/scheduled-jobs',
{
method: 'POST',
headers: {
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
body: JSON.stringify({
agent_id: 'agent_001',
skill_category: 'quickbooks',
skill_name: 'profit-and-loss-report',
skill_version: 'v1.0',
cron_expression: '0 9 * * 1-5',
input: {
accounting_method: 'Accrual',
date_range: 'last_7_days',
},
enabled: true,
}),
}
);
const job = await response.json();
console.log(job.id); // e.g., "sj_f4e3d2c1"
Python
import requests
response = requests.post(
'https://api.horizonplatform.ai/api/scheduled-jobs',
headers={
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
json={
'agent_id': 'agent_001',
'skill_category': 'quickbooks',
'skill_name': 'profit-and-loss-report',
'skill_version': 'v1.0',
'cron_expression': '0 9 * * 1-5',
'input': {
'accounting_method': 'Accrual',
'date_range': 'last_7_days',
},
'enabled': True,
}
)
job = response.json()
print(job['id'])
// 201 Created
{
"id": "sj_f4e3d2c1",
"agent_id": "agent_001",
"skill_category": "quickbooks",
"skill_name": "profit-and-loss-report",
"skill_version": "v1.0",
"cron_expression": "0 9 * * 1-5",
"input": {
"accounting_method": "Accrual",
"date_range": "last_7_days"
},
"enabled": true,
"last_run_at": null,
"last_run_status": null,
"next_run_at": "2026-03-19T09:00:00Z",
"created_at": "2026-03-18T15:00:00Z",
"updated_at": "2026-03-18T15:00:00Z"
}

GET/api/scheduled-jobs

List all scheduled jobs for the authenticated organization.

Requires authentication via x-api-key header.

curl
curl -X GET https://api.horizonplatform.ai/api/scheduled-jobs \
-H "x-api-key: hz_live_abc123def456"
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/scheduled-jobs',
{ headers: { 'x-api-key': 'hz_live_abc123def456' } }
);
const jobs = await response.json();
Python
import requests
response = requests.get(
'https://api.horizonplatform.ai/api/scheduled-jobs',
headers={'x-api-key': 'hz_live_abc123def456'}
)
jobs = response.json()
// 200 OK
[
{
"id": "sj_f4e3d2c1",
"agent_id": "agent_001",
"skill_category": "quickbooks",
"skill_name": "profit-and-loss-report",
"skill_version": "v1.0",
"cron_expression": "0 9 * * 1-5",
"enabled": true,
"last_run_at": "2026-03-18T09:00:00Z",
"last_run_status": "completed",
"next_run_at": "2026-03-19T09:00:00Z",
"created_at": "2026-03-15T10:30:00Z",
"updated_at": "2026-03-18T09:00:12Z"
}
]

GET/api/scheduled-jobs/:jobId

Retrieve details of a single scheduled job, including its last run status and next scheduled execution.

Requires authentication via x-api-key header.

Path Parameters

ParameterTypeDescription
jobId requiredstringThe scheduled job identifier.
curl
curl -X GET https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1 \
-H "x-api-key: hz_live_abc123def456"
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1',
{ headers: { 'x-api-key': 'hz_live_abc123def456' } }
);
const job = await response.json();
Python
import requests
response = requests.get(
'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1',
headers={'x-api-key': 'hz_live_abc123def456'}
)
job = response.json()
// 200 OK
{
"id": "sj_f4e3d2c1",
"agent_id": "agent_001",
"skill_category": "quickbooks",
"skill_name": "profit-and-loss-report",
"skill_version": "v1.0",
"cron_expression": "0 9 * * 1-5",
"input": {
"accounting_method": "Accrual",
"date_range": "last_7_days"
},
"enabled": true,
"last_run_at": "2026-03-18T09:00:00Z",
"last_run_status": "completed",
"last_run_job_id": "job_9a8b7c6d",
"next_run_at": "2026-03-19T09:00:00Z",
"created_at": "2026-03-15T10:30:00Z",
"updated_at": "2026-03-18T09:00:12Z"
}

The last_run_status field reflects the outcome of the most recent execution and can be one of: completed, failed, active, or null if the job has never run.


PUT/api/scheduled-jobs/:jobId

Update a scheduled job's configuration. All body fields are optional — only provided fields are changed.

Requires authentication via x-api-key header.

Path Parameters

ParameterTypeDescription
jobId requiredstringThe scheduled job identifier.

Request Body

ParameterTypeDescription
agent_idstringReassign the job to a different agent.
skill_categorystringChange the target skill category.
skill_namestringChange the target skill name.
skill_versionstringChange the skill version.
cron_expressionstringUpdate the cron schedule (UTC).
inputobjectReplace the input parameters for the skill.
enabledbooleanEnable or disable the job without deleting it.
curl
curl -X PUT https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1 \
-H "x-api-key: hz_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"cron_expression": "0 8 * * 1-5",
"enabled": false
}'
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1',
{
method: 'PUT',
headers: {
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
body: JSON.stringify({
cron_expression: '0 8 * * 1-5',
enabled: false,
}),
}
);
const updated = await response.json();
Python
import requests
response = requests.put(
'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1',
headers={
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
json={
'cron_expression': '0 8 * * 1-5',
'enabled': False,
}
)
updated = response.json()

DELETE/api/scheduled-jobs/:jobId

Permanently delete a scheduled job and remove its BullMQ repeatable entry.

Requires authentication via x-api-key header.

Path Parameters

ParameterTypeDescription
jobId requiredstringThe scheduled job identifier.
curl
curl -X DELETE https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1 \
-H "x-api-key: hz_live_abc123def456"
JavaScript
await fetch(
'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1',
{
method: 'DELETE',
headers: { 'x-api-key': 'hz_live_abc123def456' },
}
);
// 204 No Content on success
Python
import requests
response = requests.delete(
'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1',
headers={'x-api-key': 'hz_live_abc123def456'}
)
# 204 No Content on success

A successful deletion returns 204 No Content. The corresponding BullMQ repeatable job is removed immediately and no further executions will be queued.


  1. Job creation — When you create a scheduled job, Horizon persists the configuration in Supabase and registers a BullMQ repeatable job keyed by the job ID.
  2. Queue dispatch — At each scheduled time, BullMQ enqueues a job onto the skill execution queue. The worker picks it up just like any other skill execution request.
  3. Execution — The agent executes the skill with the configured input parameters. A new conversation is created for each execution, which you can query via the Conversations API.
  4. Status tracking — After each run, the last_run_at, last_run_status, and last_run_job_id fields are updated on the scheduled job record.
  5. Retries — Failed jobs are retried up to 3 times with exponential backoff (managed by BullMQ). If all retries are exhausted, last_run_status is set to failed.

Status Error Description
400 validation_error Invalid request body, missing required fields, or malformed cron expression.
401 authentication_required Missing or invalid API key.
403 insufficient_scope API key lacks the required scope.
404 not_found The specified scheduled job does not exist.
409 conflict A scheduled job with the same agent, skill, and cron expression already exists.
429 rate_limit_exceeded API key rate limit exceeded.