Skip to main content
A schedule runs an agent automatically on a recurring trigger — no human in the loop. You give it a prompt, a trigger (when to fire), and a deliver_to (where the output lands), and HQ runs the agent on that cadence and delivers the result. This guide walks the full lifecycle: create, observe, pause/resume, fire once on demand, edit, and tear down. All routes live under https://api.hq.zone/v1/api/schedules and authenticate with a personal access token: Authorization: Bearer hq_pat_.... Read operations need the schedules:read scope; everything that mutates a schedule needs schedules:write.

Create a recurring run

1

Pick a trigger

The trigger field is a tagged object — a kind plus its parameters. For a recurring run you’ll use one of:
  • cron — a 5-field cron expression (min hour dom month dow) in a named timezone. tz is an IANA name like "Europe/Stockholm"; fixed offsets such as "UTC+3" are rejected.
  • rrule — an RFC 5545 RRULE for patterns cron can’t express (e.g. “third Tuesday of the month”), also with an IANA tz.
{ "kind": "cron",  "expr": "0 8 * * MON", "tz": "Europe/Stockholm" }
{ "kind": "rrule", "expr": "FREQ=WEEKLY;BYDAY=MO,WE,FR", "tz": "Europe/Stockholm" }
tz is required on the engine-side trigger. Pass an IANA zone name explicitly — a fixed UTC offset is refused because it can’t track DST.
2

Choose where output is delivered

deliver_to is also a tagged object. The supported destinations are:
  • { "kind": "web_inbox" } — the user’s web inbox (read via the /v1/api/conversations surface).
  • { "kind": "slack_dm", "user_id": "U0123..." } — DM a Slack user.
  • { "kind": "slack_channel", "channel_id": "C0123..." } — post in a Slack channel.
  • { "kind": "teams_dm", "conversation_id": "..." } — a Microsoft Teams chat.
  • { "kind": "teams_channel", "team_id": "...", "channel_id": "..." } — a Teams channel.
3

POST the schedule

POST /v1/api/schedules creates a schedule owned by the caller. The body is:
  • name (required) and optional description.
  • trigger (required) — the object from step 1.
  • prompt (required) — the instruction the agent runs on each fire.
  • agent_id (optional) — which agent runs the prompt. If omitted, the workspace’s default enabled agent is used; a non-existent agent_id returns 404.
  • deliver_to (required) — the destination from step 2.
  • reset_each_fire (optional, default false) — false reuses one persistent conversation thread across fires (so the agent can compare against prior runs); true starts a fresh conversation each fire.
  • delivery_locale (optional) — a BCP-47 locale override for this schedule’s deliveries; falls back to the owner’s workspace locale when absent.
curl -X POST https://api.hq.zone/v1/api/schedules \
  -H "Authorization: Bearer hq_pat_..." -H "Content-Type: application/json" \
  -d '{
        "name": "Monday revenue digest",
        "description": "Weekly QoQ revenue summary",
        "trigger": { "kind": "cron", "expr": "0 8 * * MON", "tz": "Europe/Stockholm" },
        "prompt": "Summarise revenue changes quarter over quarter and flag anomalies.",
        "deliver_to": { "kind": "web_inbox" },
        "reset_each_fire": false
      }'
# → 201 { "schedule": { "id": "<SCHEDULE_ID>", "state": "active", "next_fire_at": "...", ... } }
A successful create returns 201 with the full schedule object. If your plan’s schedule limit is reached, the request fails with 402.
See the full request and response shapes on Create a schedule.

List and inspect

GET /v1/api/schedules returns your schedules plus a total count for pagination. By default scope=mine returns only the caller’s own schedules.
curl https://api.hq.zone/v1/api/schedules \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "schedules": [ ... ], "total": 3, "limit": 100, "offset": 0 }
Each entry carries observability fields: state, last_fire_at, last_fire_status, next_fire_at, fire_count, failure_count, consecutive_failure_count, last_fire_lag_ms, and last_run_conversation_id (a link to the conversation from the most recent run). By default the list excludes archived schedules so you see the working set. To change that:
  • state=<value> filters by an explicit lifecycle state (passes through verbatim).
  • include_archived=true adds archived rows to the default view (the audit history view).
  • limit defaults to 100 (max 500); offset paginates the workspace view.
To fetch one schedule’s full detail by id, use GET /v1/api/schedules/{id}. You can read a schedule only if you own it or are an admin; otherwise it returns 404.
curl https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID> \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "id": "<SCHEDULE_ID>", "state": "active", ... }

Lifecycle states

A schedule’s state is one of:
  • active — firing on its trigger.
  • paused — temporarily stopped; resumable.
  • quarantined — its stored trigger or args won’t parse; the engine parked it.
  • archived — cancelled; will never fire again (history retained).

Pause and resume

Pause an active schedule to stop it firing until you resume it. POST /v1/api/schedules/{id}/pause moves it from activepaused and clears its next fire.
curl -X POST https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID>/pause \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "ok": true }
POST /v1/api/schedules/{id}/resume brings it back and returns the newly computed next fire time.
curl -X POST https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID>/resume \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "ok": true, "next_fire_at": "..." }
You can’t resume an archived schedule. A quarantined schedule must have its trigger fixed via an update before it will resume — resuming it as-is would just re-quarantine on the next fire.
Only the schedule’s owner or an admin may pause or resume; others get 403.

Fire once now

POST /v1/api/schedules/{id}/fire_now triggers an immediate run without changing the regular firing cadence.
curl -X POST https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID>/fire_now \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "ok": true }
Owner or admin only (403 otherwise). See Fire a schedule now.

Update a schedule

PATCH /v1/api/schedules/{id} changes only the fields you supply: name, description, trigger, or args. When the trigger changes, the response includes the recomputed next_fire_at.
curl -X PATCH https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID> \
  -H "Authorization: Bearer hq_pat_..." -H "Content-Type: application/json" \
  -d '{ "trigger": { "kind": "cron", "expr": "0 9 * * MON", "tz": "Europe/Stockholm" } }'
# → 200 { "ok": true, "next_fire_at": "..." }
For description specifically, sending an explicit null clears it, while omitting the field keeps the current value.
Owner or admin only (403 otherwise). See Update a schedule.

Tear down: cancel, then erase

Teardown is a deliberate two-step path so you can’t destroy history by accident.
1

Cancel (archive)

DELETE /v1/api/schedules/{id} cancels the schedule and archives it: it flips statearchived, drops pending timers, and stops all future runs while retaining its history.
curl -X DELETE https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID> \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "ok": true }
2

Erase (permanent)

DELETE /v1/api/schedules/{id}/erase permanently deletes the schedule row. The engine refuses to erase a schedule that isn’t already archived — you must cancel it first.
curl -X DELETE https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID>/erase \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "ok": true }
Erase is destructive and irreversible. It only operates on archived rows; attempting to erase an active, paused, or quarantined schedule is rejected — cancel it first.
Both cancel and erase are owner-or-admin only (403 otherwise). References: Cancel a schedule and Erase a schedule.

Admin: filter by owner

Admins can list across the whole workspace with scope=workspace (non-admins get 403), optionally narrowing to one owner via owner_user_id. To populate a “filter by user” dropdown with only users who actually have schedules, call GET /v1/api/schedules/owners.
curl https://api.hq.zone/v1/api/schedules/owners \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "owners": [ { "user_id": "...", "display_name": "...", "schedule_count": 4 }, ... ] }
This endpoint is admin-only (403 otherwise) and returns each distinct owner with a schedule_count. See List schedule owners.