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

# Agent Schedules

> Run Orq.ai agents on a recurring or one-off cadence using cron, interval, or @at expressions — without holding open an HTTP connection.

Agent schedules let you execute an agent on a recurring or one-off cadence. A scheduled run is equivalent to calling [`POST /v3/router/responses`](/docs/agents/responses-api) with `model: agent/<key>` — same execution path, same tracing, same billing — just fired by the scheduler rather than by a client request.

Use them for:

* Nightly briefings, hourly summaries, daily digests
* One-off follow-ups at a specific time
* Background agents that don't need to be driven by user traffic

## Schedule types

| Type       | Expression                      | Example                    | Fires                        |
| ---------- | ------------------------------- | -------------------------- | ---------------------------- |
| `interval` | `@every <duration>`             | `@every 6h`                | Every 6 hours after creation |
| `cron`     | 6-field cron (seconds required) | `0 0 9 * * mon-fri`        | 9:00:00 UTC Mon–Fri          |
| `once`     | `@at <RFC3339-UTC>`             | `@at 2026-05-01T09:00:00Z` | Exactly once at that moment  |

Predefined cron descriptors also work: `@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly`.

<Warning>
  **Minimum interval is 1 hour.** Any schedule that would fire more frequently than once per hour is rejected at validation time — including cron expressions like `*/5 * * * * *` (every 5 seconds) or `@every 30m`. `once` schedules are exempt. If you need to run an agent in response to an event rather than on a clock, call the [Responses API](/docs/agents/responses-api) directly from your trigger.
</Warning>

All timestamps are UTC. Expressions in any other timezone are rejected.

## Quick start

Create an hourly schedule for an existing agent:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.orq.ai/v3/agents/customer_digest/schedules \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "interval",
    "expression": "@every 1h",
    "payload": {
      "input": "Summarize new tickets from the last hour"
    }
  }'
```

Response:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "_id": "01KPN29WWKSK0VDPJNTKZPVNRB",
  "workspace_id": "...",
  "agent_key": "customer_digest",
  "type": "interval",
  "expression": "@every 1h",
  "is_active": true,
  "generation": 1,
  "payload": { "input": "Summarize new tickets from the last hour" },
  "trigger_count": 0,
  "created": "2026-04-20T10:00:00Z",
  "updated": "2026-04-20T10:00:00Z"
}
```

The agent will now run every hour with the provided input. Each run appears in your traces as a `schedule.<agent_key>` leading span — identical structure to an HTTP-invoked agent run.

## Payload

The `payload` object is what the agent receives on each firing. All fields are optional.

| Field              | Type            | Description                                                                                             |
| ------------------ | --------------- | ------------------------------------------------------------------------------------------------------- |
| `input`            | string or array | Same shape as the [Responses API `input`](/docs/agents/responses-api#multimodal-input) — text or items. |
| `variables`        | object          | Template variable substitution. Supports secret values `{"secret": true, "value": "..."}`.              |
| `memory_entity_id` | string          | Memory store entity to attach, see [Memory](/docs/agents/responses-api#memory).                         |
| `metadata`         | object          | Opaque key/value pairs attached to every response generated by this schedule.                           |

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "cron",
  "expression": "0 0 9 * * mon-fri",
  "agent_tag": "v2",
  "payload": {
    "input": "Generate the morning briefing for {{region}}",
    "variables": { "region": "EMEA" },
    "memory_entity_id": "mem_entity_123",
    "metadata": { "run_source": "daily-briefing" }
  }
}
```

`agent_tag` pins the schedule to a specific agent version. Omit it to always run the agent's current active version.

## CRUD

All endpoints are workspace-scoped via the API key. Each schedule belongs to exactly one agent (identified by `agent_key` in the URL).

### Create

`POST /v3/agents/{agent_key}/schedules`

Required: `type`, `expression`, `payload`. Returns `201 Created` with the stored schedule, or `400` for invalid expressions, `404` if the agent doesn't exist.

### List

`GET /v3/agents/{agent_key}/schedules`

Returns `{ "schedules": [...] }`. Schedules are listed in creation order, most recent first.

### Get

`GET /v3/agents/{agent_key}/schedules/{schedule_id}`

Returns the full schedule document, including `trigger_count`, `last_triggered_at`, and `generation`.

### Update

`PATCH /v3/agents/{agent_key}/schedules/{schedule_id}`

Partial update. Any omitted field is left unchanged. Fields you can patch:

* `type`, `expression` — changing either reschedules the job, bumps `generation`, and resets `trigger_count` to 0.
* `is_active` — `false` stops future firings; `true` resumes or reactivates.
* `agent_tag` — change the pinned version.
* `payload` — update the input, variables, memory, or metadata.

Payload-only and `agent_tag`-only changes do **not** reset the firing cadence — they apply to the next regularly-scheduled run. Expression/type changes shift the cadence starting from the PATCH time.

### Delete

`DELETE /v3/agents/{agent_key}/schedules/{schedule_id}`

Returns `204 No Content`. Permanently removes the schedule in a single call.

### Trigger on demand

`POST /v3/agents/{agent_key}/schedules/{schedule_id}/execution`

Runs the schedule's payload immediately (≈10 seconds after the request, the minimum dispatch margin). The schedule itself is unaffected — its regular cadence continues. Useful for:

* Smoke-testing a new schedule without waiting for its next firing
* Manually re-running a missed execution
* UI "Run now" buttons

Returns `202 Accepted` with `{ "status": "triggered", "schedule_id": "..." }`. Inactive schedules return `400`.

## Lifecycle semantics

### `is_active` flips automatically for `once` schedules

After a `once` schedule fires, the scheduler records the trigger and flips `is_active` to `false`. The record stays available via `GET` so you can inspect what ran and when. To re-run it, either create a new schedule or PATCH `is_active: true` with a fresh `@at` timestamp in the future.

### Agent deletion cascades

Deleting an agent (`DELETE /v2/agents/{agent_key}`) asynchronously removes all schedules attached to it. You don't need to clean up schedules before deleting the agent.

### Inactive schedules don't fire

When `is_active` is `false`, the schedule stops firing entirely. You can still PATCH its fields (payload, expression) while inactive; the new values take effect on the next `is_active: true` transition.

### Missed firings are not replayed

If orq.ai is unavailable when a schedule would fire, that firing is lost. There's no catch-up. Cron and interval schedules resume on their next scheduled time once service is restored.

## Observability

Every scheduled run emits:

* A **trace** with a `schedule.<agent_key>` leading span. The span carries `orq.schedule_id`, the full agent execution chain as children, and the same `gen_ai.*` / `openresponses.*` attributes you get from HTTP-invoked runs. Searchable by `schedule_id` in the [traces UI](/docs/observability/traces).
* An **entity event** with action `triggered` when execution starts, then one of `completed`, `errored`, or `skipped` when it ends. `skipped` covers inactive schedules, stale generations (the schedule was updated between the trigger being published and consumed), and deleted agents.
* **Field updates** on the schedule document: `last_triggered_at` (UTC timestamp) and `trigger_count` (monotonic counter, reset on expression/type change).

Cost, tokens, and model are attached to the leading span the same way they appear for HTTP runs, so schedule-driven spend shows up in your usage reports automatically.

## Worked examples

### Daily morning digest (weekdays at 9am UTC)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.orq.ai/v3/agents/ops_digest/schedules \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "cron",
    "expression": "0 0 9 * * mon-fri",
    "agent_tag": "v2",
    "payload": {
      "input": "Generate the morning briefing for {{region}}",
      "variables": { "region": "EMEA" },
      "metadata": { "run_source": "daily-briefing" }
    }
  }'
```

### Follow-up in 24 hours

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.orq.ai/v3/agents/support_bot/schedules \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "once",
    "expression": "@at 2026-04-21T09:00:00Z",
    "payload": {
      "input": "Check in on ticket TICKET-123 and post a status update."
    }
  }'
```

### Hourly background summarizer (minimum allowed cadence)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.orq.ai/v3/agents/knowledge_indexer/schedules \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "interval",
    "expression": "@every 1h",
    "payload": {
      "input": "Fetch new entries and update the knowledge base",
      "memory_entity_id": "mem_entity_123"
    }
  }'
```

### Scheduled run with secret variables

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.orq.ai/v3/agents/daily_sync/schedules \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "cron",
    "expression": "0 0 3 * * *",
    "payload": {
      "input": "Sync new rows from {{table}} to the analytics warehouse",
      "variables": {
        "table": "orders",
        "warehouse_token": { "secret": true, "value": "sk-secret-123" }
      }
    }
  }'
```

Secrets are redacted from traces and stripped from the stored payload's observable form, same as the [Responses API](/docs/agents/responses-api#variables-and-secrets).

### Update only the payload (cadence unchanged)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X PATCH https://api.orq.ai/v3/agents/ops_digest/schedules/01KPN29WWKSK0VDPJNTKZPVNRB \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payload": {
      "input": "Generate the morning briefing for {{region}} — include active incidents",
      "variables": { "region": "EMEA" }
    }
  }'
```

### Deactivate and reactivate

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# Stop future firings — the record is preserved, just won't fire.
curl -X PATCH https://api.orq.ai/v3/agents/ops_digest/schedules/01KPN29WWKSK0VDPJNTKZPVNRB \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'

# Resume with the current (possibly-updated) values.
curl -X PATCH https://api.orq.ai/v3/agents/ops_digest/schedules/01KPN29WWKSK0VDPJNTKZPVNRB \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": true }'
```

### List all schedules for an agent

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl https://api.orq.ai/v3/agents/ops_digest/schedules \
  -H "Authorization: Bearer $ORQ_API_KEY"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "schedules": [
    {
      "_id": "01KPN29WWKSK0VDPJNTKZPVNRB",
      "type": "cron",
      "expression": "0 0 9 * * mon-fri",
      "is_active": true,
      "trigger_count": 12,
      "last_triggered_at": "2026-04-20T09:00:02Z"
    }
  ]
}
```

### Run now (smoke test or retry)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.orq.ai/v3/agents/ops_digest/schedules/01KPN29WWKSK0VDPJNTKZPVNRB/execution \
  -H "Authorization: Bearer $ORQ_API_KEY"
```

Returns `202 Accepted`. The run appears in traces \~10 seconds later as `schedule.ops_digest`. Regular cadence is unaffected.

### Delete

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X DELETE https://api.orq.ai/v3/agents/ops_digest/schedules/01KPN29WWKSK0VDPJNTKZPVNRB \
  -H "Authorization: Bearer $ORQ_API_KEY"
```

Returns `204 No Content`.

## Error reference

| Status | Code                 | Cause                                                                                 |
| ------ | -------------------- | ------------------------------------------------------------------------------------- |
| 400    | `invalid_expression` | Malformed cron, interval, or `@at` timestamp; or fires more often than once per hour. |
| 400    | `invalid_request`    | Unknown `type`; missing required field.                                               |
| 400    | `schedule_inactive`  | `POST /execution` on an inactive schedule.                                            |
| 404    | `agent_not_found`    | `agent_key` (or `agent_tag`) doesn't exist in the workspace.                          |
| 404    | `schedule_not_found` | Schedule ID doesn't exist, or belongs to a different agent.                           |

## Limits

* **Minimum firing interval:** 1 hour (interval + cron). `once` schedules have no minimum.
* **Maximum schedules per agent:** unlimited.
* **Maximum payload size:** same as the Responses API request body (1 MB).
