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

# Scheduler manifest

> The scheduler_blob_deployment body: schedules, their targets, and what is checked before a deploy

A scheduler manifest is the unit of work for every command in this group. It names one scheduler blob and one
revision, and lists every schedule that belongs on that revision. There is no per-schedule manifest and no default
path — `-f/--file` is required everywhere, and the file it names is loaded before anything else happens.

The file is parsed by extension: `.yaml`, `.yml` and `.json` all work, and the same manifest written in YAML or in
JSON loads identically. An unsupported extension is `FORMAT_UNSUPPORTED`; a file that does not parse is
`FORMAT_PARSE_ERROR`.

## The shared base

`scheduler_blob_deployment` is the **second** manifest type over the shared base the
[workflow manifest](/cli/workflow/manifest#the-shared-base) introduces. The base is parsed and validated by
itself, before the body is looked at, and `type` is what selects the body — so these four fields mean here exactly
what they mean there.

```yaml theme={null}
type: scheduler_blob_deployment
version: "1.0"
blob: acme-corp/checkout-ops
revision: latest
```

| Field      | Value                              | Notes                                                                                                                                                                                                  |
| ---------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`     | `scheduler_blob_deployment`        | Selects the body shape. A manifest of another type reaching a `scheduler` command is `MANIFEST_TYPE_MISMATCH`, and the same applies in reverse.                                                        |
| `version`  | `"1.0"`                            | The only version this CLI accepts. Anything else is `MANIFEST_VERSION_UNSUPPORTED`. Quote it — unquoted, YAML reads `1.0` as a float and the field must be a string.                                   |
| `blob`     | `<org>/<blob>`, or a bare `<blob>` | The **scheduler** blob. At most one `/`. A bare name inherits its org from `--org` or `BLOBHUB_ORG`; with neither available the command stops with `MANIFEST_INVALID` naming all three ways to fix it. |
| `revision` | `latest`, or a revision UUID       | Checked offline: anything that is neither is `MANIFEST_INVALID` naming the value.                                                                                                                      |

The blob's shape is confirmed before any engine call: a `scheduler` manifest pointed at a workflow blob stops with
`BLOB_NOT_WORKFLOW` — one shared resolver raises that code for every group, and its message names both the shape
found and the shape expected.

A scheduler blob is created in phase `managed` and holds a **single revision, always**, so `revision: latest` is
the normal value and names the one revision there is. Pinning a UUID is legal and does exactly what it does in the
`workflow` group.

## The `scheduler_blob_deployment` body

One key: `schedules`, a list.

Unlike the workflow body's `definitions`, an **empty list is valid** — a revision holding no schedules is an
ordinary state, and an empty `schedules: []` is precisely the document `pull` writes for one. A missing key is
still `MANIFEST_INVALID`: absent is a manifest that says nothing about schedules, empty is one that says there are
none.

| Field             | Required             | Meaning                                                                                                                                                                                    |
| ----------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `alias`           | Yes                  | The schedule's alias on the revision, and the manifest's identity for it. See [Constraints](#four-constraints-checked-before-anything-is-written) below.                                   |
| `repeat`          | Yes                  | `one_time` or `recurring_cron`. Anything else is `MANIFEST_INVALID`.                                                                                                                       |
| `cron_expression` | For `recurring_cron` | The six-field EventBridge cron body, without the `cron(...)` wrapper — for example `0 9 * * ? *`.                                                                                          |
| `invocation_time` | For `one_time`       | The single moment the schedule fires, as an ISO 8601 datetime **without** a timezone suffix — `2026-09-01T14:00:00`, not `…Z`. It is a naive time, read in this schedule's own `timezone`. |
| `timezone`        | Yes                  | An IANA timezone, for example `America/New_York` or `UTC`. Both cron expressions and one-time times are evaluated in it.                                                                   |
| `enabled`         | No                   | Boolean, defaulting to `true`. A string `"true"` is `MANIFEST_INVALID` — quote nothing here.                                                                                               |
| `start_date`      | No                   | ISO 8601 in **UTC**, unlike `invocation_time`. Before it, the schedule does not fire.                                                                                                      |
| `end_date`        | No                   | ISO 8601 in **UTC**. After it, the schedule does not fire; a recurring schedule then moves to `completed`.                                                                                 |
| `target`          | Yes                  | What to run when it fires.                                                                                                                                                                 |

The CLI carries every time and date field through as an opaque string and checks none of their contents, so the
naive/UTC split above is the platform's rule, enforced where the schedule is written rather than where it is
authored.

Unlisted keys are ignored rather than rejected, so a typo'd field name is silently inert. There is no schema
beyond what is described here.

### `repeat` decides which timing field exists

`repeat` gives exactly one of `cron_expression` and `invocation_time` meaning, and the manifest must carry that
one and only that one. A `recurring_cron` entry without a `cron_expression`, or a `one_time` entry without an
`invocation_time`, is `MANIFEST_INVALID`. So is an entry carrying **both**.

Refusing both is worth the extra rule. The likeliest way to arrive there is the half-finished edit that flips
`repeat` and leaves the old kind's field behind — and the field left behind can never take effect, never be
reported as differing, and can never be removed from a deployed schedule either: `update_schedule` sets only the
keys it is sent and rejects a `null`, so a record flipped from `recurring_cron` to `one_time` keeps its stale
`cron_expression` for good. Only the field `repeat` gives meaning to is compared, by `diff` and `deploy` alike.

### The `target` block

`target` names a **workflow** blob, not the scheduler blob the manifest is about. `blob`, `revision`, `session`
and `workflow` are required; `type`, `input_data` and `description` are not.

| Field         | Value                              | Meaning                                                                                                                                                                                                                                                              |
| ------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`        | `workflow`                         | Optional, and `workflow` is both the default and the only accepted value — it matches the platform's own `invocation_target_type` enum, so anything else is `MANIFEST_INVALID` rather than a forward-compatible unknown.                                             |
| `blob`        | `<org>/<blob>`, or a bare `<blob>` | The target workflow blob. A bare name inherits **the manifest's own org**, not `--org` — which is what makes promoting a whole system between orgs a one-line edit.                                                                                                  |
| `revision`    | `latest`, or a revision UUID       | The target revision. `latest` resolves at deploy time and is then stored as the resolved id: the platform records a revision id, not the word.                                                                                                                       |
| `session`     | An alias or a session id           | The session every fire appends its execution to. An alias that does not exist yet is created by `deploy`; an id that does not resolve is `SESSION_NOT_ACCESSIBLE`, since nobody can mint one client-side. A session that exists but is closed is `SESSION_NOT_OPEN`. |
| `workflow`    | A definition alias                 | Checked against the target revision's `workflow`-category definitions only — a `playground` definition of the same alias does not count. Stored as the alias and re-resolved at every fire, never pinned to an id.                                                   |
| `input_data`  | A list                             | Passed through verbatim to `create_execution` on every fire. Defaults to `[]`. Anything that is not a list is `MANIFEST_INVALID`.                                                                                                                                    |
| `description` | A string                           | Optional. This is the description given to each **execution** the schedule creates, not a description of the schedule. Bounded at 256 characters.                                                                                                                    |

`input_data` is the one field nothing checks until the schedule fires. The scheduler's own schema types it as a
bare array, so `create_schedule` accepts any list at all; the item shape —
`{"alias": "<component alias>", "value": {"type": …}}`, documented under
[Create Execution](/blob-types/workflow/operations/create-execution) — is enforced by `create_execution` at fire
time, where a violation surfaces as an `invocation_error` on the schedule's execution history rather than as a
failed deploy.

## Four constraints, checked before anything is written

These four are answered offline, at load time, before a single schedule is created or updated. They are checked
locally rather than left to the server for one reason: `deploy` writes entries in order, so a manifest whose
*second* entry breaks a rule the platform enforces would deploy the first and then fail — leaving a half-deployed
set that is already firing on its cron.

| Constraint         | Rule                                                                                                                                                                                                                                                                                                                                                               | Code                                                |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
| Alias grammar      | `alias` must match `^[a-z0-9_-]{6,42}$` — lowercase letters, digits, `_` and `-`, 6 to 42 characters — and must be unique across the manifest. Platform-side, a schedule alias is unique **per revision**, not per `(category, alias)` pair the way a definition alias is. An entry with no `alias` at all is a different code; a duplicate is `MANIFEST_INVALID`. | `SCHEDULE_ALIAS_INVALID`, `SCHEDULE_ALIAS_REQUIRED` |
| Target blob        | `target.blob` accepts at most one `/`. Without the check, `a/b/c` resolves as org `a` and blob `b/c`, and reaches the platform as a blob nobody has.                                                                                                                                                                                                               | `MANIFEST_INVALID`                                  |
| Target revision    | `target.revision` must be `latest` or a UUID, so a malformed value is named here rather than becoming an unreachable revision one round trip later.                                                                                                                                                                                                                | `MANIFEST_INVALID`                                  |
| Description length | `target.description` is bounded at 256 characters, the platform's own cap. Nothing else would catch it: `deploy`'s validating pass resolves the blob, revision, session and workflow alias and never looks at `description`, so an over-long one would reach the platform only in the write loop.                                                                  | `MANIFEST_INVALID`                                  |

`alias` is required on every entry, which the platform does not require of a schedule — it is the manifest's
identity for one, and what makes a second `deploy` an update rather than a duplicate.

**`cron_expression` and `timezone` are the one class of failure the CLI cannot catch beforehand.** Neither is
constrained by any schema, and the CLI passes both through as opaque strings. They are validated when the schedule
is **written**:

* The platform itself answers the two cron mistakes that are common and badly reported downstream — an expression
  that is not exactly six space-separated fields, and one where day-of-month and day-of-week are not *exactly* one
  `?` between them. Both come back as `invalid_cron_expression`. The plausible-looking `0 9 * * MON-FRI *` is the
  second of those; `0 9 ? * MON-FRI *` is the working form.
* Everything else — ranges, steps, month and day names, out-of-range values, and the timezone, which is handed
  straight to the scheduling service as its `ScheduleExpressionTimezone` — is the scheduling service's own
  rejection, surfaced with its message.

So a bad cron or timezone on a later entry *can* leave earlier entries deployed, which is the gap the constraints
above exist to keep as small as possible. Check the syntax against
[cron expressions](/blob-types/scheduler/overview#cron-expressions) before deploying a set.

## A worked example

A scheduler blob `acme-corp/checkout-ops` carrying two schedules against the workflow blob `acme-corp/checkout`:
a recurring one that runs every morning in New York time against a session it shares with every other run, and a
one-time launch pinned to a specific revision, with input data and an execution description.

<CodeGroup>
  ```yaml schedules.yaml theme={null}
  type: scheduler_blob_deployment
  version: "1.0"
  blob: acme-corp/checkout-ops
  revision: latest
  schedules:
    - alias: daily_checkout_report
      repeat: recurring_cron
      cron_expression: "0 9 * * ? *"
      timezone: America/New_York
      enabled: true
      target:
        blob: checkout
        revision: latest
        session: scheduled_runs
        workflow: checkout_flow
        input_data: []
    - alias: flash_sale_launch
      repeat: one_time
      invocation_time: "2026-09-01T14:00:00"
      timezone: UTC
      enabled: true
      end_date: "2026-09-02T00:00:00Z"
      target:
        type: workflow
        blob: acme-corp/checkout
        revision: 8f2a1c40-9d3e-4b17-9f0a-3c6f5b1d2e77
        session: flash_sale_2026
        workflow: checkout_flow
        input_data:
          - alias: campaign
            value:
              type: string
              value: autumn-flash
        description: Launches the autumn flash sale once, at 14:00 UTC.
  ```

  ```json schedules.json theme={null}
  {
    "type": "scheduler_blob_deployment",
    "version": "1.0",
    "blob": "acme-corp/checkout-ops",
    "revision": "latest",
    "schedules": [
      {
        "alias": "daily_checkout_report",
        "repeat": "recurring_cron",
        "cron_expression": "0 9 * * ? *",
        "timezone": "America/New_York",
        "enabled": true,
        "target": {
          "blob": "checkout",
          "revision": "latest",
          "session": "scheduled_runs",
          "workflow": "checkout_flow",
          "input_data": []
        }
      },
      {
        "alias": "flash_sale_launch",
        "repeat": "one_time",
        "invocation_time": "2026-09-01T14:00:00",
        "timezone": "UTC",
        "enabled": true,
        "end_date": "2026-09-02T00:00:00Z",
        "target": {
          "type": "workflow",
          "blob": "acme-corp/checkout",
          "revision": "8f2a1c40-9d3e-4b17-9f0a-3c6f5b1d2e77",
          "session": "flash_sale_2026",
          "workflow": "checkout_flow",
          "input_data": [
            { "alias": "campaign", "value": { "type": "string", "value": "autumn-flash" } }
          ],
          "description": "Launches the autumn flash sale once, at 14:00 UTC."
        }
      }
    ]
  }
  ```
</CodeGroup>

Both files load to the same manifest. The first entry's `blob: checkout` is bare and inherits `acme-corp` from the
manifest's own blob; the second qualifies the same org explicitly, which is equivalent. The first entry omits
`type` in its target, which is the same as declaring `workflow`.

The second entry's pinned `target.revision` is worth one note. `latest` and a pinned UUID both reach the platform
as a resolved revision id, so nothing in a stored schedule records which of the two the manifest wrote. `pull`
reads that back from the manifest entry it is refreshing, matched by alias — so a pin survives a `pull` instead of
being flattened to `latest` the moment it happens to name the leading revision. A schedule the manifest never
carried has no such record: it comes back as `latest` when its target revision still leads the blob, and as a
literal UUID when it does not.

## Errors

| Code                                               | When                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MANIFEST_NOT_FOUND`                               | The path passed to `-f` does not exist.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `MANIFEST_INVALID`                                 | A base field is missing or not a string; `blob` or `target.blob` has more than one `/`; `revision` or `target.revision` is neither `latest` nor a UUID; `schedules` is absent or not a list; an entry or its `target` is not a mapping; a duplicate `alias`; an unknown `repeat`; both or neither of `cron_expression`/`invocation_time` for the declared `repeat`; a non-boolean `enabled`; a missing `timezone`, `target.session` or `target.workflow`; a `target.type` other than `workflow`; a non-list `target.input_data`; a `target.description` over 256 characters; a bare `blob` with no org available. |
| `MANIFEST_TYPE_MISMATCH`                           | `type` is not `scheduler_blob_deployment`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `MANIFEST_VERSION_UNSUPPORTED`                     | `version` is not `"1.0"`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `SCHEDULE_ALIAS_REQUIRED`                          | An entry has no `alias`. The message names the offending index.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `SCHEDULE_ALIAS_INVALID`                           | An `alias` fails `^[a-z0-9_-]{6,42}$`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `SCHEDULE_TARGET_INVALID`                          | A target's blob, revision, or `workflow` alias does not resolve or is unreachable. Raised when the command runs, not at load.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `SESSION_NOT_ACCESSIBLE`, `SESSION_NOT_OPEN`       | A `target.session` id does not resolve, or the session it names is closed. These stay their own codes rather than folding into `SCHEDULE_TARGET_INVALID`, which covers only blob, revision and workflow alias.                                                                                                                                                                                                                                                                                                                                                                                                    |
| `FORMAT_UNSUPPORTED`, `FORMAT_PARSE_ERROR`         | The manifest has an unsupported extension or does not parse.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `BLOB_NOT_WORKFLOW`, `BLOB_NOT_ACCESSIBLE`         | `blob` names a blob of another domain, or one that is absent or unreachable.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `REVISION_NOT_ACCESSIBLE`, `REVISION_NOT_WRITABLE` | The revision is unreachable, or its status and phase do not allow the direction this command moves data — only `deploy` needs it writable.                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |

Every code above, with its remediation, is in [Error codes](/cli/error-codes).

## See also

* [Scheduler overview](/cli/scheduler/overview) — the two blobs one deploy touches, and the four commands that
  manage them.
* [Workflow manifest](/cli/workflow/manifest) — the same shared base, with the `workflow_blob_deployment` body
  over it.
* [Scheduler blob type](/blob-types/scheduler/overview) — cron syntax, timezones, date windowing, alias grammar
  and schedule states, as the platform defines them.
* [Concepts](/cli/concepts) — why a manifest is revision-scoped, and why drift in this group is not inert.
