Can a Sume Format package hold a product CSV? Only five file types
A Format package accepts .md, .json, .yaml, .yml and .txt files. Put the product feed row in each run's input, not in the package. Here are the limits.

No. A Format package accepts only .md, .json, .yaml, .yml and .txt files, so a .csv product feed is rejected with skill_path_invalid. Keep stable brand rules in the package and send the changing feed row as input on each run, one run per row.
What a package may contain
The Contents API applies the same rules as the dashboard editor. SKILL.md at the root is required and its frontmatter name must equal the Format slug. Other files sit at the root or one directory level under references/ or agents/. Names must match ^[A-Za-z0-9][A-Za-z0-9._-]*$ and cannot start with _ or ..
A rejected path returns skill_path_invalid with the full allowlist, and a batch that breaks a rule commits nothing.
| Rule | Value |
|---|---|
| Allowed extensions | .md, .json, .yaml, .yml, .txt |
| Directory depth | Root, or one level under references/ or agents/ |
| Size | 100 MiB per file and per package |
| Paths in one batch commit | 1000 |
| Required file | SKILL.md, name equal to the slug |
Where the feed goes instead
Per-run data belongs in input: a JSON object with at most 64 top-level keys and 2 MiB. Sume writes it whole to a file the agent reads as data, never as instructions. One CSV row becomes one object, and up to 100 rows go into one bulk queue.
A small, slow-changing lookup, such as a size chart or a list of banned claims, can live in references/ as .json or .md. A feed that changes every day should not, because each edit is a commit that bumps the Format version.
- Convert the CSV row to an object in your own code; the API does not parse CSV.
- Keep
SKILL.mda short index and move detail intoreferences/*, which cost nothing until the agent opens them. - If a feed has more than 64 columns, group them into nested objects so the top level stays under the key cap.
A worked split for a holiday feed
Say your recipe needs three things: brand voice, a banned-claims list, and one product record per run. Voice and banned claims change a few times a season, so they belong in SKILL.md and references/claims.md. The product record changes every run, so it goes in input, one object per SKU.
That split also keeps commits meaningful. Each Contents commit bumps version and produces a new package_sha, so a feed pushed into the package would make every product update look like a recipe change in your audit trail.
Tradeoff
Putting data in input means every run carries its own copy, which is more request weight than a shared file. In exchange, each run is self-describing: the first message in its thread shows exactly what the agent was given.
Sources
Related posts
More in Formats
- Sume Format run expires_at: 90 minutes, and how to set your timeout
A non-terminal Sume Format run carries an expires_at, 90 minutes from creation. Use it as your client timeout instead of inventing a number.
- Sume output schema limits: depth 10, 5,000 properties, 120,000 chars
How big can a Sume output_schema be? Depth 10, 5,000 properties and 120,000 characters, and a refusal before any spend. What to flatten and where it fails.
- Sume webhook payload is null: receipt over 1 MiB, fetch result_url
A Sume format.run.terminal webhook with payload null means the receipt was over 1 MiB. Read error.result_url and fetch the receipt instead of failing the run.
- Same Idempotency-Key on two Formats starts two runs: holiday A/B
A Sume Idempotency-Key is scoped to one Format. Reusing a key across two holiday Formats starts two runs, so make the key carry the variant.
Written by Sume