Formats

Optimistic concurrency with If-Match: edit Sume Format files by API

Edit a Sume Format's files over the API without overwriting another writer: each write carries the file's blob sha, and If-Match guards the whole package.

5 min readSume
All posts

To edit a Sume Format's files without overwriting someone else's change, read the package with GET /v1/formats/{handle}/{slug}/contents?recursive=1, then commit with PUT, sending the blob sha of each file you replace and the package sha in an If-Match header. If the file or the package moved since your read, Sume answers 409 and commits nothing.

The Contents API is shaped like GitHub's Contents API on purpose, so a coding agent can edit a Format the way it already edits a repository. The facts below come from Editing a Format package and the Sume API reference, read on 2026-09-26. For what a Format is, see What is a Sume Format?

What is optimistic concurrency, and how does Sume use it?

Optimistic concurrency means writers never take a lock. Each write names the version it was based on, and the server refuses it if that version is no longer current. The Contents API has two such checks, and they stack:

  • The per-file sha is the git blob sha of the file as stored. Replacing or deleting an existing path requires it, so two agents editing the same file cannot silently overwrite each other.
  • If-Match carries the package's own sha. Two agents editing different files each hold a per-file sha that is still current, so both writes would land; the package sha closes that gap. It is additional: per-file checks still apply.

How do I read a Format's files?

Reads need an API key with formats:read. GET …/contents lists the package root, and GET …/contents/{path} returns one file, base64-encoded, with its sha. A path that names a directory returns that directory's entries.

Add ?recursive=1 (or recursive=true) to the root listing to get every file with its body in one call, sorted by path, with no dir rows. The sha on each row is the precondition a write takes, so one recursive read is enough to start editing.

How do I commit one file, or several in one commit?

Writes need formats:write. PUT …/contents/{path} takes a message, the whole new file as base64 content, and sha when the path already exists (omit it to create a file). It is a replace, not a patch. DELETE …/contents/{path} takes message and sha, and SKILL.md cannot be deleted. The commit's author is always the key's owner; an author or committer in the body is ignored.

PUT at the package root takes a files list and writes all of them as one commit, one version bump, and one new package_sha. files is a change set: paths it does not name are kept as they are. If any entry's sha is stale, or the resulting package breaks a rule, nothing is committed.

curl -sS -X PUT "https://api.sume.com/v1/formats/acme/product-promo/contents" \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "If-Match: $PACKAGE_SHA" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "edit plan, add a note",
    "files": [
      { "path": "references/plan.md", "content": "IyBQbGFuCg==", "sha": "3f7b…" },
      { "path": "references/new-note.md", "content": "IyBOZXcK" }
    ]
  }'

How is If-Match different from an ETag?

In standard HTTP, If-Match carries an entity tag (ETag) that the server returned on an earlier response. On the Contents API it is not an opaque ETag: it is the package sha, a bare 40-character hex string. Read it from package_sha on the Format record, or from commit.tree.sha in your last write's reply; the two are the same value.

  • A stale value is refused with 409 format_package_sha_mismatch, and error.details.package_sha carries the current one, so you can re-read and retry in one round trip.
  • The header works on the root PUT, on PUT …/contents/{path}, and on DELETE …/contents/{path}.
  • Formats published before package_sha existed carry none, and they ignore the header instead of answering a 409 you could never satisfy.

Which errors should my code handle?

A write either commits or fails. There is no path where the Format changes but the commit does not exist.

From Editing a Format package, read 2026-09-26.
Status`error.code`What happened
409format_content_sha_requiredThe path already exists and you sent no sha. Read it, then retry.
409format_content_sha_mismatchYour sha is stale: someone else committed first. Re-read and retry.
409format_package_sha_mismatchYour If-Match package sha is stale. error.details.package_sha holds the current one.
400skill_path_invalid, skill_frontmatter_invalid, skill_limit_exceeded, …The resulting package broke a rule. Nothing was committed.
404format_content_not_foundThe Format exists but holds nothing at that path.
403insufficient_scopeThe key lacks the scope, or it is a service-account key, which cannot create or edit packages. Mint a new key.
503format_git_unavailablePackage history could not take the commit, so nothing was saved. Retry.

What does the Contents API not do?

It is authoring, not execution. Editing a package never touches a run already in flight, because each run reads the package it started with; Sume Format versions covers how to tell which edit a run used.

  • No public git endpoint and no clone URL. Reverting, blame, and history browsing are not part of this surface yet.
  • No partial writes: content is always the whole file.
  • No deletes inside a batch: remove a file with DELETE …/contents/{path}.
  • A batch may name at most 1000 paths, and a package holds at most 100 MiB per file and per package. The file rules are in How to write a SKILL.md recipe.

Sources

Related posts

Written by Sume