Format Contents API: read the whole package, commit many files at once
Read a Sume Format package with ?recursive=1 and write several files as one commit and one version bump. A change set, not the package; deletes stay separate.

Add ?recursive=1 to GET /v1/formats/{handle}/{slug}/contents to read every file in a Sume Format package, with bodies, in one request. To write several files, PUT to the package root with a files list: it lands as one commit, one version bump and one new package_sha, or not at all.
Both calls are in the Contents API docs, which model them on GitHub's Contents API. This post covers the two calls an agent that edits Formats uses most.
How do I read the whole package?
Listing the root and fetching each path is one request per file. The recursive listing returns every row as a file with its base64 content, sorted by path, and no dir rows, since the directories are the paths. recursive=true works too; anything else is the plain root listing. The sha on each row is the same precondition a write takes, so one recursive read is enough to start editing.
curl -sS "https://api.sume.com/v1/formats/acme/live-commerce/contents?recursive=1" \
-H "Authorization: Bearer $SUME_API_KEY" \
| jq -r '.data[] | "\(.path) \(.sha)"'How do I commit several files at once?
Send message and a files list to the root. Each entry follows the single-file rules: content is the whole file base64-encoded, and sha is required when the path exists and omitted to create it.
curl -sS -X PUT "https://api.sume.com/v1/formats/acme/live-commerce/contents" \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "edit plan + add note",
"files": [
{ "path": "references/plan.md", "content": "IyBQbGFuCg==", "sha": "3f7b1c0d9e2a4b6c8d0e1f2a3b4c5d6e7f809a1b" },
{ "path": "references/new-note.md", "content": "IyBOZXcK" }
]
}'What are the rules of a batch?
files is a change set, not the package. Paths the Format holds but the body does not name are kept exactly as they are, so editing two of twelve files never risks the other ten. Deletion is not expressible in a batch: remove a file with DELETE .../contents/{path}, which takes message and sha.
If any entry's sha is stale, or the resulting package breaks a rule, nothing is committed. A batch may name at most 1000 paths. For a guard on the whole package rather than each file, add the If-Match header with the package sha; see If-Match edits.
What may a package contain?
The same rules the dashboard editor enforces. A rejected path answers skill_path_invalid and spells the allowlist back.
| Rule | Value |
|---|---|
| Entry file | SKILL.md at the root, required, cannot be deleted; frontmatter name must equal the slug |
| Layout | Root, or one directory deep under references/ or agents/ |
| File names | Match ^[A-Za-z0-9][A-Za-z0-9._-]*$ |
| Extensions | .md, .json, .yaml, .yml, .txt |
| Size | At most 100 MiB per file and per package |
What does this API not do?
It is authoring, not execution: editing a package never touches a run already in flight, since each run reads the package it started with. There is no public git endpoint or clone URL, and reverting, blame and history browsing are not part of this surface yet. Service-account keys cannot create or edit packages, and a write needs formats:write. A failed history write answers 503 format_git_unavailable and saves nothing, so retrying is safe.
How do these calls fit an agent loop?
A coding agent that edits a Format follows one loop: read the package once with recursive=1, keep each file's sha and the Format's package_sha, plan the edit against that snapshot, then write the change set. If the package moved in between, an If-Match write 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.
- Create the Format with
POST /v1/formats; it commits a minimal validSKILL.mdimmediately. - Keep
package_shaandcontents_urlfrom the reply for your next write. - A stale per-file
shais409 format_content_sha_mismatch; a missing one on an existing path is409 format_content_sha_required. - A slug your workspace already uses is
409 skill_slug_taken, and one a Format by Sume holds is409 skill_slug_reserved.
Sources
Related posts
More in Formats
- Why your order_id comes back null in a Sume structured output
If a Sume run's filled_by is projection, the fallback never sees your input or instruction, so an order_id you sent comes back null. Keep ids on your side.
- Optional field in a Sume Format output_schema: use a null union
A Sume output_schema has no optional properties. List every key in required and give optional ones a type of ["string","null"], or the create fails with 400.
- output_schema_unsatisfied with rejected_urls: the Sume URL gate
A Format run that returns output_schema_unsatisfied and rejected_urls named a media URL it did not generate. How the URL gate works and how to fix the schema.
- Format reads inactive but still runs: the 409 codes
A never-run Format may read inactive until its first API run. The real refusal is a 409 format_inactive or format_api_trigger_disabled.
Written by Sume