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)
│ │ │ │ │
* * * * *
ExpressionDescription
0 9 * * 1-5Every weekday at 9:00 AM
*/15 * * * *Every 15 minutes
0 0 1 * *First day of each month at midnight
30 17 * * 5Every 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 required string The agent that will execute the skill.
skill_category required string The skill category (e.g., 'quickbooks', 'sage-intacct', 'web').
skill_name required string The skill name in kebab-case (e.g., 'profit-and-loss-report').
skill_version string The skill version. Default: 'v1.0'.
cron_expression required string A standard five-field cron expression (evaluated in UTC).
input object Optional input parameters passed to the skill on each execution.
enabled boolean Whether 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 required string The 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 required string The scheduled job identifier.

Request Body

ParameterTypeDescription
agent_id string Reassign the job to a different agent.
skill_category string Change the target skill category.
skill_name string Change the target skill name.
skill_version string Change the skill version.
cron_expression string Update the cron schedule (UTC).
input object Replace the input parameters for the skill.
enabled boolean Enable 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 required string The 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.

StatusErrorDescription
400validation_errorInvalid request body, missing required fields, or malformed cron expression.
401authentication_requiredMissing or invalid API key.
403insufficient_scopeAPI key lacks the required scope.
404not_foundThe specified scheduled job does not exist.
409conflictA scheduled job with the same agent, skill, and cron expression already exists.
429rate_limit_exceededAPI key rate limit exceeded.