Stage a Sume Format change with no branches: second slug, If-Match
Sume Formats have no branches or revert. Test a change in a second Format slug, then promote it to the live one with a single If-Match PUT of the files.

How do I test a Format change safely when there are no branches?
The contents API reads and writes a Format's files, but it has no branches, no revert, no blame and no history browser. A live Format has one current package. If you edit it in place and the change is bad, your weekly run uses the bad package.
The workaround is a second Format. POST /v1/formats creates one, and a slug that is already taken answers 409. In its SKILL.md, the frontmatter name must equal the slug, so copy the files and change that one line.
What are the steps?
- Read the live Format's files with
GET /v1/formats/{handle}/{slug}/contentsand note itspackage_sha. - Create
weekly-promo-nextwithPOST /v1/formats, and set thenamein its frontmatter to match. - Edit the copy and run it a few times with real inputs, comparing receipts.
- Promote by sending the tested files to the live Format in one root
PUT, with each existing path's liveshaandIf-Matchset to thepackage_shafrom step one.
How does the promote step stay safe?
A root PUT takes a files change set applied as one commit, with base64 content. For a path the live Format already has, each entry also needs that file's current sha; a new file sends none. The change set is not the whole package: paths you leave out stay as they are, and a removed file needs its own DELETE. With If-Match: <package_sha>, a write whose base is stale is refused with 409 format_package_sha_mismatch instead of overwriting someone else's edit. When you copy SKILL.md back, restore its name to the live slug, not weekly-promo-next.
A run reads the package it started with, so promoting a change never alters a run already in flight. The receipt's format.version tells you afterward which version each run used, and the version on the Format record rises by one for each commit. Keep the staging Format for the next change instead of deleting it; it is a cheap, permanent test bench and costs nothing until you run it.
import base64, os, requests
BASE = "https://api.sume.com/v1/formats/acme/weekly-promo/contents"
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
def promote(files: dict, package_sha: str):
# files: path -> (text, live_file_sha or None for a new file)
changes = []
for path, (text, sha) in files.items():
entry = {"path": path,
"content": base64.b64encode(text.encode()).decode()}
if sha:
entry["sha"] = sha
changes.append(entry)
r = requests.put(BASE, json={"files": changes}, timeout=30,
headers={**H, "If-Match": package_sha})
if r.status_code == 409:
raise RuntimeError("live Format changed; re-read and retry")
r.raise_for_status()
return r.json()
Sources
Related posts
- Optimistic concurrency with If-Match: edit Sume Format files by API
- Create an AI video workflow via API: a Sume Format and its SKILL.md
- Version control for AI agent prompts in production: Sume Formats
- Edit a Format mid-batch: which version do queued items use?
- Detect that someone edited your Sume Format before the weekly run
More in Formats
- Stocking stuffer ads for 100 products: one bulk run, 16 at a time
One Sume bulk run takes 1-100 items and 1-16 parallel workers. Here is how to set it up for a stocking stuffer catalog and check the failed count at the end.
- Sume bulk item expires_at: 90 minutes start at dispatch, not at submit
A Sume run's expires_at is 90 minutes from created_at. A bulk child is created when dispatched, so a queued item's clock has not started.
- Sume schedule cron: why @weekly and 6-field cron are rejected
Sume schedules take only standard 5-field cron. Macros like @weekly and 6-field expressions with seconds are rejected, so convert them before you create one.
- Sume scheduled run across daylight saving: Monday 9am New York
A Sume schedule takes a 5-field cron and an IANA timezone. Across the 2026 clock changes a Monday 9am New York job fires once, and its UTC hour shifts by one.
Written by Sume