-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
Four fields sit at the top of every manifest the CLI reads, whatever blob domain it belongs to. The base is parsed and validated by itself, before the body is looked at;type is what selects the body.
All four must be non-empty strings. That is the whole base — the scheduler group’s manifest carries exactly these
four fields and then its own body, and a future blob domain does the same. Nothing on the shared side changes
when one arrives.
blob
acme-corp/checkout qualifies the org in the file. A bare checkout does not, and inherits the org from the
global --org option or the BLOBHUB_ORG environment variable — which org a checkout belongs to is a property
of the checkout, not of the key being used to reach it, so a profile is deliberately not a source for it.
With a bare name and neither supplied, the manifest is MANIFEST_INVALID and the message names all three ways
to fix it, since two of them are not in the file in front of you.
An org-scoped API key checks its target as a raw string against the org UUID. Addressing that org by its
alias yields a confusing 403 on every call, so under an org-scoped key qualify the manifest — or --org — with
the UUID.
The blob’s shape is confirmed before any engine call: a workflow manifest pointed at a blob of another domain
stops with BLOB_NOT_WORKFLOW rather than an opaque 500 one round trip later.
revision
latest resolves through the blob’s latest_revision_id at the moment the command runs. A pinned UUID names one
revision for as long as the manifest says so.
Which revisions a command will accept depends on the direction it moves data:
The CLI fetches the revision and applies this itself, so a refusal names the phase and points at
diff. The platform’s own rejection is a bare 400 that does not mention the
revision at all. build is exempt from all of it — it never resolves a revision.
Every path anchors to the manifest’s own directory
source, code.base_path and value.source all resolve against the directory holding the manifest file, never
against the current working directory and never against each other. A manifest is revision-scoped, and its
definitions are typically scattered across several directories, so a source-relative anchor would make the
meaning of a ../.. depend on which subdirectory a given definition happened to sit in.
entry_point is the one exception: it resolves against base_path, because it names a module inside the Python
package base_path roots rather than a file in the project.
Paths are collapsed lexically, so .. segments work and are resolved without touching the filesystem.
The workflow_blob_deployment body
One key: definitions, a list with at least one entry. An empty or absent list is MANIFEST_INVALID.
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.
category
Inferred from the source document’s own type: workflow_definition gives workflow, playground_definition
gives playground. One function owns this rule for every command in the group, build included, so no two
commands can disagree about what an entry is.
- Omitted, document present,
typerecognized — the inferred category wins. - Omitted, document present,
typeunrecognized —DEFINITION_CATEGORY_UNKNOWN. Declarecategory. - Omitted, document absent (before a first
pull) — defaults toworkflow. - Declared,
typeunrecognized — the declared category wins. This is what declaring it is for: atypethat names no native category contradicts nothing. - Declared, and it contradicts the document —
DEFINITION_CATEGORY_UNKNOWN, naming both.
(category, alias) pair, so one alias may
legitimately name a workflow definition and a playground definition on the same revision.
components[]
Each binding says which component it addresses, and what to put in it.
code and value are mutually exclusive; declaring both is MANIFEST_INVALID. A binding with neither resolves
the component and writes nothing, which makes it a no-op that can still fail with COMPONENT_NOT_FOUND.
A code binding is what build and
deploy act on, and the only thing the
compiler is ever pointed at. The bound component’s language port must be exactly
python; anything else, including no language port at all, is LANGUAGE_NOT_PYTHON.
A worked example
A revision holding two definitions: a workflow whoselogic.code component (id: c1) is compiled from pkg/
and whose pricing table is loaded from a JSON file, plus a playground with nothing bound into it.
pkg/entry.py — entry_point is relative
to base_path, while base_path itself, source and value.source are all relative to the manifest.
The playground entry declares no category, so it is inferred from its document’s type. It declares no
components either: playground definitions are moved verbatim by
pull and push, and nothing compiles into them.
Errors
Every code above, with its remediation, is in Error codes.
See also
- Workflow overview — the shape of a deployable project and the ten commands that move it.
- Compiler — what a
codebinding produces, and every check it runs first. - Sandbox — what the compiled code executes against.
- Concepts — why a manifest is revision-scoped, and why category participates in identity.
- Workflow definition format — the document a
sourcepoints at.

