-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 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.
The blob’s type is confirmed before any data command: 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: default 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.
A schedule entry carries exactly the create_schedule fields, under the platform’s own names:
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.
Every level of this document rejects a key it does not list — an entry’s own fields, and likewise
invocation_target’s and its target’s — so a typo’d field name is MANIFEST_INVALID rather than 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 invocation_target block
invocation_target is the API’s own object, carried into the manifest field for field — the same one
Create Schedule and
Invocation Targets describe. It has exactly three keys, and
neither it nor its nested target admits one it does not list.
target has the following fields. Each part present is a non-blank string of at most 64 characters, checked
before any of the rules below; a blank or over-long value is MANIFEST_INVALID naming the field.
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 — 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.
The retired layout
The retired layout put a schedule entry’s owntarget: block one level up, with workflow as a path string and
a blob addressed as an org/blob string inside it. Every one of these is refused at load — MANIFEST_INVALID,
before any network call — each message naming the field’s new home. Only an entry-level target: hints
blobhub scheduler pull -f <path>, since pull reads only the manifest’s base and can safely rewrite the whole
entry from what is deployed. A retired key found inside a target that already sits under invocation_target, a
string invocation_target.workflow, or an org/blob string in invocation_target.target.blob hints
blobhub scheduler diff -f <path> instead and is fixed by hand, because pull would replace the file and drop
its undeployed entries.
An entry-level
target: is caught before the generic unknown-field check, so its message names the new layout
rather than listing target as an unrecognized field. A retired key found inside a target that already sits
under invocation_target, a string invocation_target.workflow, and an org/blob string in
invocation_target.target.blob are each caught in their own check and hint diff, not pull — the same remedy
latest → default uses elsewhere in this manifest — since only an entry-level target: is safe for pull to
rewrite wholesale.
Seven constraints, checked before anything is written
These seven 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.
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 asinvalid_cron_expression. The plausible-looking0 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.
A worked example
A scheduler blobacme-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 a description.
org: acme-corp beside the
alias blob: checkout — unlike the retired layout, nothing here inherits the manifest’s own top-level blob’s
org, so a target blob named by alias always spells its own org out.
The second entry’s pinned target.revision is worth one note. It is stored on the schedule exactly as the
manifest wrote it: default stays the literal word, and a pinned UUID stays that UUID — the platform never
resolves one into the other, so pull reads a target back unambiguously as following or pinned, matched by the
schedule’s alias, with no guessing involved.
A cross-blob workflow
invocation_target.workflow can name a definition outside the target blob entirely:
checkout; the definition it runs comes from shared-procedures. See
The workflow reference.
Errors
Every code above, with its remediation, is in Error codes.
See also
- Scheduler overview — the two blobs one deploy touches, and the four commands that manage them.
- Workflow manifest — the same shared base, with the
workflow_blob_deploymentbody over it. - Scheduler blob type — cron syntax, timezones, date windowing, alias grammar and schedule states, as the platform defines them.
- Concepts — why a manifest is revision-scoped, and why drift in this group is not inert.

