API
Distribution formats: ZIP archives and a Claude Code marketplace
Deterministic ZIP archives you can pin by SHA-256 and a hosted Claude Code plugin marketplace per owner, for tools that do not speak the ReuseForge API.
Two endpoints serve tools that understand archives or Claude Code marketplaces but do not speak this API. Both are part of contract 1.1.0 and are built from the same stored files as the JSON bundle, with the same access rules.
| Need | Endpoint |
|---|---|
One version as a ZIP you can unzip, cache in CI, or pin by hash | GET /api/v1/items/{owner}/{slug}/versions/{version}/archive.zip |
| Install an owner's free skills, prompts and MCP servers in Claude Code | GET /api/v1/marketplaces/{owner}.json |
Claude Code: add a marketplace
/plugin marketplace add https://reuseforge.com/api/v1/marketplaces/<owner>.json
/plugin install <slug>@reuseforge-<owner>The marketplace is named reuseforge-<owner> (what you type after @). It lists the owner's public, free, active items of kind skill, prompt and mcp_server, one plugin per item, named by its slug. Every entry is an archive source pinned by SHA-256, so Claude Code refuses an archive whose bytes differ from what was listed:
{
"name": "reuseforge-janedoe",
"description": "Free skills, prompts and MCP servers published by Jane Doe on ReuseForge.",
"owner": { "name": "Jane Doe", "url": "https://reuseforge.com/app/janedoe" },
"plugins": [
{
"name": "invoice-skill",
"source": {
"source": "archive",
"url": "https://reuseforge.com/api/v1/items/janedoe/invoice-skill/versions/1.2.0/archive.zip?layout=claude-plugin",
"sha256": "<64 hex characters>"
},
"description": "Extract line items from invoices.",
"version": "1.2.0",
"category": "skill",
"tags": ["invoices"],
"author": { "name": "Jane Doe" },
"homepage": "https://reuseforge.com/app/janedoe/invoice-skill",
"license": "MIT"
}
]
}What is left out, on purpose:
- paid, private, unlisted, draft, archived and retracted items (a paid archive cannot be fetched by Claude Code anyway: it has no way to answer a 402);
- kinds that are not Claude Code plugins (
repo,dataset, ...); - items whose latest version is yanked or a prerelease (the highest stable live version is pinned instead; an item with none is skipped), and versions that cannot be wrapped (for example a skill without
SKILL.md); - slugs that would read as Anthropic's own names (
claude-*,anthropic-*).
An owner without matching items still gets a valid document with "plugins": [].
Each listing pins an exact version. After the owner publishes a new version, the marketplace follows within a minute (Cache-Control: public, max-age=60); run /plugin marketplace update reuseforge-<owner> to refresh sooner. The first time a version appears, its archive is built to learn the digest. At most 8 are built per request; if more are outstanding the response carries X-ReuseForge-Marketplace-Pending: <n>, a 30 second cache lifetime, and the next fetch finishes the list.
Download a version as a ZIP
curl -fLO https://reuseforge.com/api/v1/items/janedoe/invoice-skill/versions/1.2.0/archive.zip
curl -fL -H "Authorization: Bearer $REUSEFORGE_TOKEN" -o pro.zip \
https://reuseforge.com/api/v1/items/janedoe/paid-tool/versions/latest/archive.zipWith the CLI: reuseforge versions download janedoe/invoice-skill 1.2.0 --format zip. With the SDKs, client.get_version_archive("janedoe/invoice-skill", "1.2.0") (Python) and client.getVersionArchive(...) (JavaScript) return the raw bytes (bytes / Uint8Array).
The ZIP is deterministic: the same version always yields the same bytes, so the hash can be pinned in a lockfile, a CI cache key or a plugin marketplace.
- store-only (no compression), entries sorted by the UTF-8 bytes of their path, no directory entries, no extra fields, no data descriptors;
- every timestamp is 1980-01-01 00:00:00;
- file mode
0644, or0755for paths the version'sreuseforge.jsonlists underexecutable(globs*,**,?); setuid, setgid and sticky bits are never set; - names are marked UTF-8, and paths that are absolute, contain
.., empty segments, backslashes or control characters are never written.
Response headers: Content-Type: application/zip, Content-Length, ETag (the quoted SHA-256), X-Content-SHA256 (lower-case hex), Content-Disposition: attachment; filename="<slug>-<version>.zip". Send If-None-Match to get a 304. Verify a download:
curl -fsSD headers.txt -o x.zip "$URL"
sha256sum x.zip # equals the X-Content-SHA256 header and the ETag
unzip -l x.zipItemVersion.archive_sha256 carries the digest of the default layout once it has been built (it is null before the first download and always null for paid items), so a hash can be read from the version metadata without downloading.
Pinning
Fetch the version once, record its digest, and check it on every later fetch. A version never changes, so a mismatch means the bytes were altered in transit or at rest:
want=<sha256 recorded earlier>
curl -fsSL -o x.zip "$URL" && echo "$want x.zip" | sha256sum -c -A pinned digest stays valid across ReuseForge releases that touch the archive writer only if the writer's output for that version is unchanged; the writer is versioned (format) and a change to the bytes is a contract change announced in the API changelog.
The claude-plugin layout
?layout=claude-plugin (kinds skill, prompt, mcp_server; others answer 400 invalid_parameter) wraps the version as a Claude Code plugin, with its own digest:
| Kind | Contents |
|---|---|
| all | .claude-plugin/plugin.json with name (the slug) and version, plus description, license, homepage, keywords when the publish manifest has them |
skill | the files under skills/<slug>/ (a SKILL.md at the root of the version is required) |
prompt | the main markdown file as commands/<slug>.md, other files under files/<slug>/ |
mcp_server | the files under servers/<slug>/ and a .mcp.json generated from the mcp section of the publish manifest; paths to shipped files become ${CLAUDE_PLUGIN_ROOT}/servers/<slug>/..., ${VAR} placeholders are kept |
A version that does not fit (a skill without SKILL.md, an MCP server without a valid mcp section, a prompt with no identifiable main file) answers 400 with the reason.
Access
Archives follow the JSON bundle exactly, and are never partial:
| Caller | Free public version | Paid version |
|---|---|---|
| anyone | 200 | 402 payment_required unless every file is a free preview |
| signed in, no purchase | 200 | 402 with an order_hint that says what to order |
| buyer, owner account member | 200 | 200 |
A 402 body is a normal Problem. order_hint.create_order is the request that buys the version. Private and unlisted items answer 404 to callers who cannot see them, and retracted items 410 (members and earlier buyers of a paid version keep access). Downloads are counted once per viewer and day; the read rate tier applies.
Last updated: