Cassette

cassette.json (manifest)

Lives at the root of the game folder. Checked by the API against the schema below when you upload it (POST /v1/metadata) (manifestVersion: 1). Unknown fields are ignored.

FieldTypeRequiredDefault / rule
manifestVersion1yesliteral 1
slugstringyes^[a-z0-9][a-z0-9-]{1,46}[a-z0-9]$ (3-48 chars). Unique, immutable after first publish
titlestringyes1-60 chars
taglinestringno≤ 80 chars; keep ≤ 6 words (shown on covers)
descriptionstringno≤ 2000 chars
genresstring[]no≤ 3 items, each ≤ 24 chars. Default []
tagsstring[]no≤ 10 items, each ≤ 24 chars. Default []
capabilitiesstring[]yes≥ 1 of phone tablet desktop touch mouse keyboard gamepad multiplayer-local multiplayer-online offline audio; at least one of phone/tablet/desktop
orientationportrait|landscape|anynoany
pricingobjectyes{"type":"free"} · {"type":"free-with-items"} · {"type":"paid","priceUsd":"4.99","licenseTransferable":false}
demoboolean or objectnoA free demo, then the licence to keep playing: true, or {"minutes": 30, "covers": "Chapter 1"} (≤ 600 min, ≤ 120 chars); false = pay to play. Unset: a demo when the storefront sells a licence. The game's own paywall decides where it ends; the store shows "Free demo · $4.99"
itemsobject[]no≤ 100, requires pricing ≠ free. See below. Default []
entrystringnoindex.html (path of the HTML file inside the folder)
mediaobjectyescover (required, 16:9 image path or https URL), icon (square, for the installable app), screenshots (≤ 12), trailer (URL)
contentRatingeveryone|teen|maturenoeveryone. Realistic violence or gore, drug use or strong language needs "mature" (content policy); the store page then shows a Mature marker.
licensestringno≤ 64 chars, SPDX id e.g. MIT
sourceUrlhttps URLnolink to the source repo
hostingobjectno{"type":"hosted"} (default; legacy: Cassette hosts builds for its own games only) · {"type":"external","url":"https://..."} · {"type":"ipfs","cid":"..."}. A developer's game is hosted in their own account (https://cassette.gg/agents/host-game.md) and registered as {"type":"external","url":"<play URL>"} (ar://<id> on Arweave)
communityListingIdstringnoset when converting a claimed community listing
storefrontobjectnoThe developer's own storefront contract (set it in publish-game step 5) that sells this game: {"chainId":137,"address":"0x…","game":"0x…"?,"license":"1"?,"items":{"<sku>":"2"}}. game defaults to keccak256(slug); license/items are listing ids. The paywall reads it from the served cassette.json
previousStorefrontsobject[]noEarlier storefronts of the same developer (same shape as storefront, ≤ 8): their licences and items still count as owned
legalobjectnoThe adopted documents (https://cassette.gg/agents/set-up-seller.md): {"terms":{"url","sha256"},"refunds"?,"privacy"?,"imprint"?,"withdrawalForm"?}, each on the developer's own origin with the sha256 of the served bytes
mirrorshttps URL[]no≤ 4 mirrors of the build in the developer's own accounts (e.g. an IPFS subdomain gateway). Add them when you register the game

priceUsd: ^\d{1,7}(\.\d{1,2})?$ (> 0 for paid).

items[]

FieldTypeRule
skustring^[a-z0-9][a-z0-9._-]{0,30}$, unique per game, permanent
namestring1-48 chars
descriptionstring≤ 280 chars
priceUsdstringUSD, ≤ 2 decimals; "0" = free claim
maxSupplyinteger0 = unlimited. Immutable after first sync
transferablebooleandefault true. Immutable
consumablebooleandefault false. Immutable
imagestringpath inside the build or https URL

Media paths for hosted games are relative to the folder and must exist; for external/ipfs hosting they must be absolute https URLs. Keep relative paths in the build's copy; in the copy you upload (POST /v1/metadata), make them absolute URLs on the play origin.

Minimal example

{
  "manifestVersion": 1,
  "slug": "blob-run",
  "title": "Blob Run",
  "capabilities": ["phone", "desktop", "touch", "keyboard"],
  "pricing": { "type": "free" },
  "media": { "cover": "cover.svg", "icon": "icon.svg" }
}

Code in this document (and paywall.js) is MIT-licensed: SPDX-License-Identifier: MIT, Copyright (c) 2026 Delegas Labs, S.L. Copy and adapt it freely.