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.
Cron Expression Format
Section titled “Cron Expression Format”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.
Create a Scheduled Job
Section titled “Create a Scheduled Job”/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
| Parameter | Type | Description |
|---|---|---|
| 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. |
Request
Section titled “Request”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 }'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"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'])Response
Section titled “Response”// 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"}List Scheduled Jobs
Section titled “List Scheduled Jobs”/api/scheduled-jobs List all scheduled jobs for the authenticated organization.
Requires authentication via x-api-key header.
Request
Section titled “Request”curl -X GET https://api.horizonplatform.ai/api/scheduled-jobs \ -H "x-api-key: hz_live_abc123def456"const response = await fetch( 'https://api.horizonplatform.ai/api/scheduled-jobs', { headers: { 'x-api-key': 'hz_live_abc123def456' } });
const jobs = await response.json();import requests
response = requests.get( 'https://api.horizonplatform.ai/api/scheduled-jobs', headers={'x-api-key': 'hz_live_abc123def456'})
jobs = response.json()Response
Section titled “Response”// 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 a Scheduled Job
Section titled “Get a Scheduled Job”/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
| Parameter | Type | Description |
|---|---|---|
| jobId required | string | The scheduled job identifier. |
Request
Section titled “Request”curl -X GET https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1 \ -H "x-api-key: hz_live_abc123def456"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();import requests
response = requests.get( 'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1', headers={'x-api-key': 'hz_live_abc123def456'})
job = response.json()Response
Section titled “Response”// 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.
Update a Scheduled Job
Section titled “Update a Scheduled Job”/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
| Parameter | Type | Description |
|---|---|---|
| jobId required | string | The scheduled job identifier. |
Request Body
| Parameter | Type | Description |
|---|---|---|
| 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. |
Request
Section titled “Request”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 }'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();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 a Scheduled Job
Section titled “Delete a Scheduled Job”/api/scheduled-jobs/:jobId Permanently delete a scheduled job and remove its BullMQ repeatable entry.
Requires authentication via x-api-key header.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| jobId required | string | The scheduled job identifier. |
Request
Section titled “Request”curl -X DELETE https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1 \ -H "x-api-key: hz_live_abc123def456"await fetch( 'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1', { method: 'DELETE', headers: { 'x-api-key': 'hz_live_abc123def456' }, });// 204 No Content on successimport requests
response = requests.delete( 'https://api.horizonplatform.ai/api/scheduled-jobs/sj_f4e3d2c1', headers={'x-api-key': 'hz_live_abc123def456'})# 204 No Content on successResponse
Section titled “Response”A successful deletion returns 204 No Content. The corresponding BullMQ repeatable job is removed immediately and no further executions will be queued.
How Scheduling Works
Section titled “How Scheduling Works”- 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.
- 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.
- Execution — The agent executes the skill with the configured
inputparameters. A new conversation is created for each execution, which you can query via the Conversations API. - Status tracking — After each run, the
last_run_at,last_run_status, andlast_run_job_idfields are updated on the scheduled job record. - Retries — Failed jobs are retried up to 3 times with exponential backoff (managed by BullMQ). If all retries are exhausted,
last_run_statusis set tofailed.
Error Responses
Section titled “Error Responses”| 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. |