The creator flow
Plan-only by default
Without--live a commerce step quotes $0, submits nothing, and reports what it would
stage:
zap run <slug> looks
in the current project’s agent/skills/ first and falls back to the bundled registry;
zap add zap-merch-drop copies the recipe into your project when you want to edit it.
Live runs
--live runs must execute inside the creator’s air box, where the Hermes runtime has
already written ~/.hermes/.env with the box gateway (OPENAI_BASE_URL ending in
/api/gateway/v1 and OPENAI_API_KEY as the gateway token). The commerce step derives
the air API base and token from those values; a plain OpenAI key is never reused. Outside
a box, set ZAP_AIR_API_BASE, ZAP_AIR_GATEWAY_TOKEN, and optionally
ZAP_AIR_CATALOG_PATH explicitly. With none of these configured the step fails closed
with COMMERCE_UNCONFIGURED before touching the catalog.
The gateway token only ever travels to the host that issued it: setting ZAP_AIR_API_BASE
to anything other than the gateway host requires an explicit ZAP_AIR_GATEWAY_TOKEN, and
the API base must be https:// (plain http:// is accepted only for localhost /
127.0.0.1 / ::1; anything else fails with COMMERCE_INSECURE_API_BASE).
The hosted runner (zap.wzrd.tech) refuses a live run that contains any commerce step at
submission time — before credentials are resolved or any media step is sent to a
provider — because staging belongs to the box that owns the catalog. Dry runs still plan.
Each live run:
- Publishes the generated image through
POST /api/media/publishwhen possible so the listing gets an R2 public URL (air drops any other image host tonull). Only image files (.png .jpg .jpeg .webp .gif) under the run’s assets directory, the project directory, or~/.hermes/inboxare eligible; anything else stages without an image. - Upserts one catalog entry by
key— re-running the same Zap updates the listing rather than duplicating it. The read-modify-write holdscatalog.json.lockso concurrent runs cannot drop each other’s listings, and a catalog that exists but cannot be read or parsed aborts the run (COMMERCE_CATALOG_UNREADABLE) instead of being overwritten. - Files
publish_catalog. air deduplicates an already-pendingshop_publishdecision, so repeated runs reuse it (decisionReused: true). Because of that, a request whose reply is lost is retried once; if it times out again the run fails withCOMMERCE_STAGE_TIMEOUTand says air may already hold the decision — running again converges on it and nothing is charged.commerce.payment_requestis never retried, since a repeat would file a second request: any lost reply — a timeout, a dropped connection, or a200whose body never arrives — is reported as possibly filed and is not marked retryable; check Needs You first.
Writing a listing
sanitizeCatalogItem: key is derived from the name unless set
(^[a-z0-9][a-z0-9_-]{0,63}$), names are ≤200 chars, descriptions ≤2000. image must
reference an image step that appears earlier in the file, or a declared type: image
input; validation rejects a reference to a later step.
Bundled recipes
merch-drop— selfie/product photo + name + price →physicallisting.event-ticket— event details + poster prompt + price →event_ticketlisting; fulfilment mints and scans ticket codes through air’s existing checkout webhook.
Invariants
- No Zap step charges or moves money;
charges: falseis emitted on every commerce result. - Catalog publishing is owner-approved (
shop_publish) before anything reaches buyers. - Prices are server-derived from
storefront_productsat checkout, never client-supplied. commerce.payment_requestlikewise only files apayment_requestdecision.
