> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hq.zone/llms.txt
> Use this file to discover all available pages before exploring further.

# Schedule a recurring agent run

> Create autonomous schedules that fire an agent on a cron or RRULE trigger, then list, pause, fire on demand, and tear them down.

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

<Steps>
  <Step title="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`.

    ```json theme={null}
    { "kind": "cron",  "expr": "0 8 * * MON", "tz": "Europe/Stockholm" }
    { "kind": "rrule", "expr": "FREQ=WEEKLY;BYDAY=MO,WE,FR", "tz": "Europe/Stockholm" }
    ```

    <Note>`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.</Note>
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.

    ```bash theme={null}
    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`.
  </Step>
</Steps>

<Tip>See the full request and response shapes on [Create a schedule](/api-reference/schedules/create-schedule).</Tip>

## 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.

```bash theme={null}
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`.

```bash theme={null}
curl https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID> \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "id": "<SCHEDULE_ID>", "state": "active", ... }
```

<Tip>Reference: [List schedules](/api-reference/schedules/list-schedules) and [Get a schedule](/api-reference/schedules/get-schedule).</Tip>

### 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 `active` → `paused` and clears its next fire.

```bash theme={null}
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.

```bash theme={null}
curl -X POST https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID>/resume \
  -H "Authorization: Bearer hq_pat_..."
# → 200 { "ok": true, "next_fire_at": "..." }
```

<Note>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.</Note>

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.

```bash theme={null}
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](/api-reference/schedules/fire-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`.

```bash theme={null}
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": "..." }
```

<Note>For `description` specifically, sending an explicit `null` clears it, while omitting the field keeps the current value.</Note>

Owner or admin only (`403` otherwise). See [Update a schedule](/api-reference/schedules/update-schedule).

## Tear down: cancel, then erase

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

<Steps>
  <Step title="Cancel (archive)">
    `DELETE /v1/api/schedules/{id}` cancels the schedule and **archives** it: it flips `state` → `archived`, drops pending timers, and stops all future runs while retaining its history.

    ```bash theme={null}
    curl -X DELETE https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID> \
      -H "Authorization: Bearer hq_pat_..."
    # → 200 { "ok": true }
    ```
  </Step>

  <Step title="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.

    ```bash theme={null}
    curl -X DELETE https://api.hq.zone/v1/api/schedules/<SCHEDULE_ID>/erase \
      -H "Authorization: Bearer hq_pat_..."
    # → 200 { "ok": true }
    ```
  </Step>
</Steps>

<Warning>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.</Warning>

Both cancel and erase are owner-or-admin only (`403` otherwise). References: [Cancel a schedule](/api-reference/schedules/cancel-schedule) and [Erase a schedule](/api-reference/schedules/erase-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`.

```bash theme={null}
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](/api-reference/schedules/list-owners).
