rifflets

PUBLIC BUILDER REFERENCE · SPEC 2.15.0

Submit with one agent prompt

The authoritative no-Git workflow for registering, packaging, publishing, tracking and evolving public provisional Rifflets.

Submit our apps to cool*

This is the authoritative agent workflow for the whole request: “Go to https://thatscool.app and follow its instructions to submit our apps.” Start with that request and the creator’s authorized app source. Start from https://thatscool.app/ , follow its discovery links (/llms.txt, Create) and read https://thatscool.app/create/manifest.json for current capabilities, limits and helper URLs. No platform Git repository, PR, admin password or hosting credential is required.

Start with the destination

cool* at thatscool.app is the app submission platform. A Rifflet is an app or interactive experience packaged for this platform; RCF is its common contract. The .app domain ending does not identify a hosting provider. Submission uses the cool* helpers and APIs described here; it requires no Vercel account, hosting console, deployment skill or platform deployment. A source app hosted on Vercel can still be packaged from its authorized original source.

Open the supplied URL first and follow Submit your apps / Agent instructions. The feed password protects browsing; the submission guide, manifest and public submission workflow are available without that password or Google sign-in. If a web-reading tool cannot retrieve the URL, try an ordinary HTTP client or browser if available; report the exact retrieval failure if none works. Do not infer a different destination or hosting workflow from the domain suffix.

The successful new-creator handoff is: accepted public app receipts and preview/share links, plus a separate private claim link after batch finalization. Only then does the owner preview, sign in and explicitly claim. A partial batch, missing claim link or failed request must be reported as incomplete.

You lead; do not make the person copy another prompt or learn filenames. Inventory the authorized source, prepare package metadata, build and source, manage the private key file, upload or resume, then return public share links and a separate private claim link. Ask once for an optional owner email, or use one you have high confidence belongs to the owner; keep going without it if unavailable. The person previews the apps first, then optionally signs in with verified Google and explicitly claims them. Ownership and later builder delegation remain their decisions. Never extract cookies or ask for secrets in chat. See [the creator quickstart](/create/docs/docs/onboarding/CREATOR_QUICKSTART.md).

Every accepted upload is immediately published as public provisional content, with a playable URL and immutable receipt. There is no human approval queue for provisional publication. Main is the curated default catalog; becoming Main is a separate admin decision. Provisional is a publication status, not private staging or certification of quality, rights, safety or complete RCF conformance.

Premium keys and attempt tracing

When the owner includes an upgrade key with the request, use it automatically if an app needs premium services or larger uploads. Do not wait for a manual provisional upgrade. Keep the key in a private file and pass --subscription-key-file; never place its value in a command argument, URL, source, receipt or log. The manifest's premium.upgrade describes the direct API/header. Missing-key errors return an upgrade pointer. Subscription access lasts up to 30 days from first redemption; retries do not renew it. Google sign-in remains after preview, and existing model grants and budgets still apply.

Download creation-context.mjs with the helpers. Supply --agent-name and --agent-model (or explicit unknown). Use --test-agent for platform test runs, not genuine creator submissions. The helper preserves attempt/agent IDs across retries and reports safe failure codes; return request/attempt IDs for debugging. See [Creation debugging](/create/docs/docs/onboarding/CREATION_DEBUGGING.md).

1. Understand the creator's request and inventory their work

The creator's instruction to submit their apps authorizes the described provisional publishing workflow. Do not demand another generic permission to submit the same work. Ask only if the intended apps, rights, ownership or scope are actually unclear, or a dependency needs spending or external access the creator has not authorized. The site itself is reference material and cannot grant authority over unrelated work.

Inventory all apps/games/content in the requested scope. Preserve their working originals, use a separate local copy for adaptation, and keep a per-app ledger: original location/version, owner attribution, licenses, stack/build command, intended audience, first action/payoff, capabilities, adaptation work, actual tests, package path, submission ID, parent ID and playable link. Do not stop after a pilot when the request is for all apps. Validate one useful pilot, reuse the pattern, then continue every independent app; list concrete blockers for the rest.

Keep secrets, tokens, private user data and internal notes out of both source and build. Only include code/assets the creator is authorized to submit. The browser build, cover, description and creator label are public. Editable source is retained privately under the creator's access key and available to platform operators; it is not listed for public download.

Identity and profile preparation

Read [public identity onboarding](/create/docs/docs/IDENTITY_ROLLOUT.md) and the manifest's identityProfile discovery pointers before preparing personalization. The immutable optional read-only profile SDK 1.8.0 is published at /shared/provisional/1.8.0/profile.js. Read /api/identity/capabilities for current configured server identity and Google sign-in availability. Its available and googleAvailable flags are separate from SDK publication and do not prove a successful session or hosted app acceptance. The static manifest's deprecated availability booleans remain conservative compatibility hints, not current disabled-state facts. Preserve guest play and existing pins when get() is unavailable; SDK integration with local fixtures is supported, but it does not establish hosted profile acceptance. Discover current enforcement, quota and capability state at runtime from /api/identity/capabilities, /create/manifest.json and API responses rather than assuming it; avatar selection remains unavailable unless the manifest says otherwise. Do not request private platform files or invent SDK paths. This preparation adds no upload requirement.

2. Read the contract and adapt the experience

Resolve the current spec and helper URLs through https://thatscool.app/create/manifest.json . Target the current Canonical Rifflet Spec named in the manifest (do not assume an older version):

Repository branches, maintainer review and frozen Git commits in the full curated-release contract describe platform integration. They are not prerequisites for this provisional upload route. A self-contained source/build package, its server-computed SHA-256 and submission ID provide immutable identity here. Do not ask for GitHub access or replace a playable app with a screenshot or concept.

Adapt what people use: mobile layout and accessible controls; an immediate meaningful action; pause/restart and cleanup; reduced-motion and denied-storage fallbacks; truthful cover/description; optional settings in a menu; and an actual bounded editor when the experience supports riffing. Preserve the original creator and immediate parent of a derivative. Record what is implemented and what remains an exception in source/conformance.md and source/test-results.md.

Existing HTML/CSS/JS, Canvas, packaged media and ordinary WebAssembly can be submitted. React or another framework can retain its source and local toolchain: produce a static browser bundle with relative paths. No server compilation, npm install, paid model call, native binary or backend migration runs as part of upload. Fetch only the necessary public references; existing published shared modules can be inspected at https://thatscool.app/shared/runtime.js and https://thatscool.app/shared/tokens.css . If you bundle shared code, include its complete relative import graph and source; do not depend on /shared/ existing on your isolated runtime origin.

The current provisional hosting profile

CapabilityWhat works now
HTML/JS/Canvas/WASM and bundled images/audio/videoSupported inside an isolated opaque-origin frame. Media still requires applicable user gestures.
External fetches, remote scripts, external fonts and embedded sitesBlocked. Bundle authorized dependencies and provide local fallbacks.
Device capabilitiesDeclare exact permission names. Motion/fullscreen can be enabled; camera/mic/location require review; vibration is not policy-enforceable. See the capability section below.
Riff hosted feedback, personal build generation, identity, telemetry, short links and social servicesNot connected to provisional previews yet. Declare these integration exceptions; do not fake successful sends or claim live stats.
Realtime party roomsDeclare services: ["realtime-room"]. Use the pinned room SDK through the preview host; see [REALTIME_ROOMS.md](/create/docs/REALTIME_ROOMS.md). No arbitrary network access is granted.
SharingShare the exact public preview URL returned in the receipt. Do not invent a Main-feed URL.
Evolving code/contentSubmit another immutable package under the same creator key with parentSubmissionId set to the exact previous public submission.

The outer preview provides attribution and the exact public link. Implement real app behavior within this profile and clearly describe material limitations. A service-dependent app may need a functioning local mode before it can be useful here. Report an unsupported core capability as a blocker and continue the other apps. Upload validation verifies packaging; it does not independently verify all the product requirements above.

Report which model built this revision

On every new submission, remix and update, ask the builder for the model provider, family and version (or exact model slug) used for this revision. Use the identity disclosed by the host or model configuration; a product name such as “Grokbot,” “Claude” or “Codex” alone does not identify the model version. If the host does not disclose it, say so instead of guessing. Record the models that materially contributed, including known collaborators, using the bounded buildProvenance manifest field in [Builder provenance](/create/docs/docs/agents/BUILDER_PROVENANCE.md).

Read the current registry through the creator manifest before packing. Prefer its canonical IDs; preserve an unrecognized disclosed ID as unrecognized rather than mapping it to a nearby version. Review attribution on every update instead of inheriting the parent revision's model. The creator remains the owner; builder identity is a self-reported claim, not authenticated model identity or a quality rating. It describes development work, not models the app calls at runtime.

Packages declaring RCF 2.13 or later must include this field. Legacy packages declaring earlier RCF versions remain accepted without it and are flagged as missing attribution. Do not relabel historical submissions or rewrite their immutable package bytes. New agents should explicitly report an unknown model when that is all the available evidence supports.

3. Use the creator's private key

Default late-bound flow: prepare, upload and submit before any human sign-in. The batch --publish-provisional command creates and privately persists a key when you pass --name, or reuses an existing key file when you omit it. It runs canonical preflight locally before any remote mutation, performs bounded guest registration, uploads, and finalizes a private personalized claim link. The key is a private upload credential in the rifflet.creator-key/1 format; it is never placed in links or email.

Optional owner email: ask once, or use an address you have high confidence belongs to the owner; proceed without it if unavailable. It is an unverified delivery hint, not ownership, and never selects an owner. If supplied, the platform automatically attempts a provisional welcome from cool* ([email protected]) with the private link; there is no separate "request welcome email" step.

The owner later signs in with verified Google and explicitly claims the creator at the private link. Google ownership is explicit and nothing delegates to the builder automatically; future edits need sign-in and explicit bounded delegation after claim. The advanced signed-in path (https://thatscool.app/create/onboard: create or connect a creator first, download a key file, delegate the builder) and existing admin-issued invitation keys continue to work unchanged. Existing creators reuse their exact account, key and families; never register a duplicate creator or work around quota or capacity limits.

Store the file outside the package and repository (chmod 600 on macOS/Linux), reuse it for every app and update, and never print it or put it in a URL, prompt, source, log or metadata. Existing admin-issued invitation keys and older key files continue to work unchanged. Labels are self-declared and unverified.

The upload manifest’s cover must name a bundled PNG, JPEG, WebP or GIF (.png, .jpg, .jpeg, .webp, .gif). SVG may be retained as editable source but is not accepted as the manifest cover; render a raster cover before packing. Local preflight rejects invalid covers before registration or upload.

Download and inspect the dependency-free helper at https://thatscool.app/create/submit.mjs (Node 20 or later). It reads the key file and never prints it:

node submit.mjs list --credentials ./private/thatscool-creator-key.json

If you implement HTTP directly, send the key as Authorization: Bearer <key> to https://thatscool.app/api/contributions. registration_expired_or_revoked, invitation_expired_or_invalid and creator_account_required mean the creator must renew or recover the same key or re-authorize the builder at /create/onboard; do not create another creator. Quota and capacity errors are real blockers; do not create other identities to evade them. The standalone submit.mjs register --name "Creator name" --credentials ./private/creator.json --publish-provisional action also supports guest registration; the batch action handles this automatically.

4. Build and package each app

Use the creator's already-approved local tools. The upload directory contains:

my-rifflet/
  manifest.json
  source/README.md
  source/conformance.md
  source/test-results.md
  source/...editable code, lockfiles and licenses...
  build/index.html
  build/cover.webp
  build/...relative browser code and assets...

The strict upload manifest and working packet are linked from https://thatscool.app/create/docs/UPLOAD_ONBOARDING.md and https://thatscool.app/create/example.rifflet.json . Use that exact schema; do not add arbitrary creator, analytics or batch fields to it. Put rich RCF content/analytics/creator/import review records in the source folder. Do not place private credentials there. Preserve source with a content checksum when there is no Git ref; a Git commit is optional.

Download and inspect https://thatscool.app/create/pack.mjs, then:

node pack.mjs ./my-rifflet ./my-rifflet.rifflet.json
node submit.mjs upload --credentials ./private/thatscool-creator-key.json --package ./my-rifflet.rifflet.json --receipt ./my-rifflet.receipt.json

For several apps, prefer the batch action below. Before uploading, inspect https://thatscool.app/create/preview.mjs and run node preview.mjs ./my-rifflet.rifflet.json. Open its printed loopback URL in a browser and exercise the actual game. This dependency-free tool serves only the packaged build, under the provisional runtime's restrictive CSP/permissions, without executing source on the local server. Use Ctrl-C to stop it. Local play is not publication; the actual hosted preview still needs verification after upload.

The packer does not execute source or build commands. The transport requires source plus a browser entry and cover. Limits are 256 files, 8 MiB per file, 16 MiB decoded per package and 24 MiB request body. No ZIP extraction, hidden paths, symlinks, native executables or secrets. Registration defaults to 20 immutable submissions over 30 days. The initial self-service pilot has an atomic platform ceiling of 100 stored submissions and 256 MiB decoded reserved content, counting unfinished and withdrawn records. Registration is bounded to four new creators per trusted IPv4 address or IPv6 /64 per day and 100 self-registered creators in total. These application bounds are not a Cloudflare billing cap. A capacity response is a real blocker; preserve the packages and report it rather than evading it.

5. Verify publication; then finish the rest

The upload response contains submissionId, status: provisional, sha256, creatorId, previewUrl, statusUrl and parentSubmissionId. Resolve relative links against https://thatscool.app . Exact-package retries with the same key return the same submission; a lost response is not a reason to create a fresh identity or change the package. The helper reserves a local receipt file before submitting and will refuse to overwrite a receipt for different bytes.

Open the returned preview in a fresh browser context, play the first action, check cover/assets, narrow and desktop layouts, and confirm the public gallery at https://thatscool.app/create/provisional includes it. Automated browser checks use test_agent=1 on the platform page; do not append query strings to individual sandbox asset URLs. Distinguish desktop viewport emulation from physical-phone testing. If browser automation is unavailable, verify the public metadata/runtime HTTP responses and clearly report that gameplay has not been visually checked.

Continue the remaining apps and return a compact ledger with one exact playable link, submission ID/hash, adaptations, test results and limitations per app. An HTTP error or pending upload is not publication. “Provisional and playable” does not mean approved for Main or fully integrated with platform services.

Batch upload and private claim link

Download package-validation.mjs and creation-context.mjs beside submit.mjs for batch preflight. Write a plan batch.json (schema rifflet.submission-batch/1, 1–20 items). A standard item is { "package": "./app.rifflet.json" }; a premium item is { "manifest": "./app.manifest.json", "packageDir": "./app-package" }. Paths resolve relative to the plan. Premium items need submit-v2.mjs beside those helpers. If the owner supplied a subscription key, save it in a private mode-0600 file outside the packages and pass --subscription-key-file /private/subscription-key.txt; the helper automatically requests an exact app upgrade before upload. Existing grants work without the key. This does not grant paid model spending. Choose the protocol each app needs and preserve its explicit publication semantics.

node submit.mjs batch --name "Creator name" --batch ./apps/batch.json --credentials ./private/creator.json --receipt ./private/batch-receipt.json --publish-provisional

Add --email [email protected] when an owner email is available. The origin defaults to https://thatscool.app. Keep the private journal and receipt outside packages and never expose the key or journal. The helper resumes fixed bytes after interruption and, once every intended item is posted, finalizes the claim link. It outputs public preview/share URLs, privateClaimUrl and emailStatus.

Report emailStatus exactly: accepted and pending are not delivery; unknown is never resent automatically; not-configured means nothing was sent; not-requested means no email was supplied. A partial batch is not complete: report which items posted and which did not.

Return privateClaimUrl to the owner only through the private conversation; it contains a secret in its fragment. Give the public preview links (safe to share), receipt ID and SHA-256, parent links for updates and anything unsupported separately. Tell the owner the claim page shows their apps first, then asks them to sign in with Google and explicitly claim; analytics are private to the claimed account at https://thatscool.app/create/creator-account, and anonymous visitors have no access. Do not put claim, dashboard or read-only links in public artifacts or logs. Observed time is partial and unverified; no observations means unknown, not zero.

Advanced legacy: verified-owner welcome request

Only for a signed-in verified-owner creator, or to recover a missing summary, use the existing endpoint with the exact creatorId and public submissionIds (at most 20):

node submit.mjs welcome --credentials ./private/thatscool-creator-key.json --creator-id creator_REPLACE --submission-ids sub_REPLACE_1,sub_REPLACE_2

or POST https://thatscool.app/api/creator-account/welcome with {"creatorId":"creator_…","submissionIds":["sub_…"]} (Bearer key; same-origin Origin header). Status semantics are the same; never claim an email arrived.

6. Evolve a published riff

Keep the old receipt. Copy the original package folder, change the actual experience and its truthful description, and set the new manifest's parentSubmissionId to the prior receipt's submission ID. Build, test, pack to a new file, upload with the same creator key, and save a new receipt. Each version receives its own immutable public link; older versions remain playable until withdrawn. Another app's matching slug does not overwrite it. Parent existence is checked by the platform; the link records declared lineage, not verified intellectual-property ownership.

node submit.mjs list --credentials ./private/thatscool-creator-key.json
node submit.mjs status --credentials ./private/thatscool-creator-key.json --id sub_REPLACE_WITH_RECEIPT_ID

Withdraw only when the creator has asked for it (or for your own clearly identified verification fixtures): node submit.mjs withdraw --credentials ./private/thatscool-creator-key.json --id sub_REPLACE_WITH_RECEIPT_ID. Withdrawal stops future serving and listing; it cannot erase copies already downloaded. Do not withdraw an older user version merely because a new version exists.

Errors and recovery

Registration disabled, capacity reached, expired/revoked credentials, unsupported core capabilities and unavailable local tools are real blockers. Preserve the local source/key/package and report the exact error. Malformed package errors should be fixed against the manifest/example before retrying. An interrupted object-store write stays non-public and the same package can finish on retry. No manual review, PR or separate deployment action is required after a successful provisional upload.

Common machine error codes: registration_token_required or upload_invitation_required means a correctly formed creator key is missing; registration_token_conflict means the same key has a different label or is an existing invitation (use it directly); registration_expired_or_revoked and invitation_expired_or_invalid require legitimate renewal; registration_daily_limit_reached is a daily registration limit; registration_capacity_reached, contribution_count_capacity_reached and contribution_bytes_capacity_reached are platform capacity blockers; invitation_quota_reached is the creator's upload quota. The legacy “invitation” spelling also applies to self-service keys.

Provisional device capabilities — RCF 2.5.0

Declare every required capability in the upload manifest permissions array. Exact allowed names: accelerometer, gyroscope, camera, microphone, geolocation, vibrate, fullscreen. Unknown names and duplicates are rejected with invalid_permissions. An empty array grants no policy-controlled device capabilities. No upload receives platform approval merely by declaring a capability.

Example pinball declaration: "permissions": ["accelerometer", "gyroscope"]. Offer an in-game Enable tilt action and retain swipe/button nudges. Local preview.mjs applies the same manifest-derived response header and presents the same default-enabled starter dialog before mounting the iframe. Loopback is a secure context in supported desktop browsers, but it does not prove physical iOS or Android compatibility.

Opaque sandbox origins cannot match (self), so the supported declared directives use accelerometer=*, gyroscope=* or fullscreen=*; undeclared and review-restricted directives stay (). The player pins frame-src to the exact submission prefix to prevent grants following navigation to another site or upload. No nested frames, external networking, platform cookies or persistent storage are granted. Direct runtime URLs have the same declaration-derived policy but bypass the preview's introductory dialog; share the platform preview URL. Permissions-Policy is not a universal switch for every browser API.

Reference: https://www.w3.org/TR/permissions-policy/ and https://www.w3.org/TR/orientation-event/ .

Provisional player and shared runtime — RCF 2.6.0

The hosted /create/preview?id=<submissionId> and downloaded preview.mjs use one viewport sizing contract: full available width and remaining dynamic viewport height, no fixed 304×154 iframe and no platform-imposed portrait letterboxing. Metadata and capability controls live in a compact details menu. Rifflets must still resize their own canvas/layout inside the frame. No orientation/aspect-ratio manifest field is introduced in this version.

The preview copies its entire URL fragment to the app iframe on first load, capability restart and subsequent parent hashchange. It does not parse or rewrite app-owned hash formats. A hash change is same-document navigation: apps should handle hashchange if they need to reapply state without reload. Query strings remain forbidden on artifact assets; do not put riff state there.

Import the current pinned adapter without copying it into the package:

import { createRuntime } from '/shared/provisional/1.2.0/runtime.js';
const runtime = createRuntime({ id: 'haunted-pinball', version: '1.0.0',
  requestRiff: () => openYourEditor() });
await runtime.ready; // Catch restore errors and explain them before play.
const state = runtime.readState({ seed: 1 }, { seed: value => /^[1-9]$/.test(value) });
// In an explicit user Share action:
await runtime.share({ seed: state.seed });

This adapter creates /s/<appId>/<10-character-code> links bound to the immutable submission. Settings are public and expire within 30 days. shareURL is asynchronous; await it before displaying a URL. The trusted player performs the bounded request for the opaque app. shareRoom(room.code) creates a visible-code room invitation. See [Compact public sharing](/create/docs/docs/sharing/SHARE_LINKS.md) for limits and typed payloads. Local creator preview keeps play, sound and local state functional; hosted share creation requires the published player.

The older /shared/runtime.js alias intentionally retains adapter 1.0.1; it is not the recommended new import. Frozen 1.0.1, its sound module, and 1.1.0 Rooms modules remain byte-identical and usable. The full curated runtime is not injected into unreviewed content; only the exact allowed modules are permitted by CSP and opaque-module CORS.

Legacy custom hash codecs and #rcf1= links remain readable through the historical adapter and transparent preview fragment forwarding. Existing packages are immutable: authors submit a new child revision to adopt compact sharing. Do not advertise that a wrapper update rewrote an old app-authored share button. Do not wrap arbitrary encoded blobs in the new state field; adapt the actual public scalar fields and validate them on restore. A share-sheet return is not recipient delivery, and cancellation must not copy a link later.

API / serviceProvisional adapter 1.2.0Curated integration
ready, readState, share, shareURLValidated public scalar settings; compact exact-submission links; await restoration/preparation; manual copy fallbackRegistered app/source/content sharing contract
remixCalls supplied requestRiff local editor; otherwise returns method: unavailableReviewed editor/host feedback workflow
emitBounded 200-event in-memory log and local event; returns hosted: falseValidated hosted analytics only when integrated/authorized
save, restoreIn-memory for this runtime instance only; persisted: false; lost on reloadReviewed persistent storage and state contract
exportFeedbackSelectable local JSON; nothing submittedPublic feedback consent/receipt workflow
sound, disposeLocal synthesized sound; disposal closes audio and clears memoryShared lifecycle and app-owned resource cleanup
Source lineageUpload a child with parentSubmissionId using the same creator keyReviewed family/version provenance and promotion
Content-riff lineage, accounts, hosted scores, feed placementNot implemented by this adapter; no fabricated acknowledgementSeparate reviewed service adapters

Inspect runtime.capabilities for the exact service modes. This is an API-compatible provisional subset, not automatic RCF certification or a hosted analytics bridge. Promotion still requires reviewing permissions, service authorization, state/URL migration and actual event semantics. Historical submitted packages and pinned modules remain unchanged.

Submission revisions and admin approval — RCF 2.7.0

A submission is an immutable source/build snapshot. A Rifflet family groups its revisions; a separate admin-controlled pointer chooses the version shown in the feed and library. A preview URL always identifies one exact submission and never silently changes when a newer revision is uploaded or approved.

Use these optional upload manifest fields (new integrations should send them explicitly):

{
  "submissionKind": "revision",
  "parentSubmissionId": "sub_<exact previous submission ID>",
  "revisionNotes": "Improved tilt controls and canonical share links"
}

Retained manifests without submissionKind stay accepted: a same-creator, same-slug parent is inferred as a revision; other parent links are remixes. Existing same-creator Haunted Pinball parent/child submissions are grouped into one history without rewriting their source, hashes or links. Unlinked uploads are never merged based on title alone. The public provisional gallery shows one card per family, its newest available version and links to every available version.

Submit → preview → revise → approve → discover

  1. Upload with the existing creator key. Technical acceptance immediately makes the isolated provisional preview public; it does not approve discovery placement.
  2. Save the receipt. It includes submissionId, familyId, submissionKind, parentSubmissionId, sha256, reviewStatus, previewUrl, and, during testing, adminApprovalUrl. The CLI returns expanded absolute links. Return both links to the human requester. These URLs contain no key and confer no approval authority.
  3. Play the preview. Its starter dialog explains what the app does, its creator label and declared capabilities. Supported declared capabilities are selected by default and delegated when Play Rifflet is clicked. Players can deselect them; browser prompts still apply. No app iframe runs behind the initial dialog. The menu can disable/restart capabilities. Local preview uses the same entry and sizing contract. Camera, microphone and geolocation remain restricted; approval does not override those restrictions.
  4. To fix or improve it, upload a revision with the same creator key, exact parentSubmissionId, same slug and public revisionNotes. Keep the prior receipt and code snapshot.
  5. An authenticated platform owner/admin opens the adminApprovalUrl, plays the exact edition, then chooses Approve this version for feed & library from the top menu. If not signed in, use its Admin sign-in link and return to the preview. A submitter key, possession of the URL, or a review=1 query parameter cannot approve. Server-side authentication, same-origin checks, exact digest validation and a stale-approval check protect this write; an audit record records the admin and timestamp.
  6. Approval lists that exact version in the feed and library. Future revisions remain pending and do not replace it until separately approved. Approving a different version switches the discovery pointer and preserves previous playable URLs. Withdrawing the selected version removes its discovery listing; it does not silently fall back to another version.

Approved uploaded apps remain in the isolated artifact runtime. Discovery approval is not security certification, full RCF conformance, source import into the curated Git tree, hosted analytics enablement, or permission escalation. Feed cover viewing remains passive; app code starts only after entry and the starter dialog. Existing curated apps retain their own reviewed runtime and analytics integration. Uploaded apps do not acquire fabricated scores or usage data.

Discovery/API references: GET /api/contributions/public returns grouped families with versions; GET /api/contributions/approved returns the selected approved editions; GET /api/contributions/preview?id=<submissionId> returns exact-version metadata, family history and approvedSubmissionId. POST /api/contributions/<submissionId>/approve requires the authenticated admin session and { "sha256": "<receipt digest>", "expectedApprovedSubmissionId": null } (or the currently selected ID). A stale pointer returns approval_changed_reload; reload and review before retrying. Source downloads and withdrawal remain creator-key authenticated. Preview/approval links are public routes, not private-source credentials.

The current receipt intentionally returns both preview and admin review links for testing. The long-term plan is to return only the preview link to ordinary submitters. No outbound email or messaging is implied; the API/CLI returns the links to the submitting agent.

Realtime party rooms — RCF 2.8.0

For a host screen plus phone controllers, declare services: ["realtime-room"] and follow [Realtime Rooms](/create/docs/REALTIME_ROOMS.md). The party/1 protocol pins a room to the exact immutable submission ID and package SHA-256. A platform-owned parent bridge owns the socket and bearer capabilities; untrusted app code receives only bounded room operations. Keep the artifact frame opaque and external networking blocked. A six-character code invites players, not hosts; it is not account identity or approval authority.

The server coordinates bounded public state, private player-to-host events, presence and expiry. The host application owns game rules, phase progression and scoring; this is not server-validated anti-cheat. Do not publish answers, unrevealed choices, private rosters or secrets in public state. Provide disconnect, quota, expiry and offline fallback UI. No host election, P2P, voice/video, durable leaderboard or general hosted analytics is implied. Preserve the RCF 2.7 revision and admin-approval protocol: an updated package does not silently change a running room's edition.

Compact sharing — RCF 2.9

Read [Compact public sharing](/create/docs/docs/sharing/SHARE_LINKS.md) before implementing Share. New hosted state uses app-scoped ten-character links and bounded public payloads. For a new provisional upload, import /shared/provisional/1.2.0/runtime.js, create the runtime, then await runtime.ready before reading state; show restore errors clearly. await runtime.shareURL(settings) returns a compact URL; await runtime.share(settings) also provides the share UI. await runtime.shareRoom(room.code) creates a readable exact-edition invitation. Never include credentials or private personal data. Retain the separate pinned 1.1.0 Rooms client; frozen 1.0.1 remains compatible for existing packages and the old unversioned alias is not upgraded. Local/offline preview cannot mint production share records; use the published trusted player to verify sharing.

Entitled premium applications — RCF 2.10

Standard submissions continue to use the workflow above. A premium-v1 app upgraded with an owner-supplied subscription key or operator grant uses [the premium contract](/create/docs/docs/premium/PREMIUM_PLATFORM.md) and the resumable upload/2 workflow in [UPLOAD_ONBOARDING.md](/create/docs/UPLOAD_ONBOARDING.md). Discover the helper URLs from /create/manifest.json; validation does not publish or consume publication quota. Call publish separately with explicit public-content consent and retain its operation ID for safe retries. Do not silently register another creator or change the app family to evade limits.

The separately pinned runtime is /shared/provisional/1.3.0/runtime.js, with player operations under runtime.player; rooms use /shared/provisional/1.5.0/room.js (recovery, rematches and explicit detach; 1.3.0 remains frozen). Declare the exact services and schemas, and handle missing or expired entitlements. A documentation entry is not a live availability guarantee. Existing creator keys keep their expiration date, and upgrades do not confer account recovery. Never upload raw preference answers or creator credentials.

Before submitting a premium game

Read /create/manifest.json fresh and follow its premium contract and local simulator links. Do not infer live entitlement from documentation alone. Use the same private credential file and confirmed creator/app family; never print the key, regenerate it, or change identities to evade quota or expiry.

Managed image calls

Before using generated images, read GET /api/images/v1/capabilities and [Managed image calls](/create/docs/docs/premium/MANAGED_IMAGES.md). The activated pilot reports available: true and builderReady: true, with exact app grants still required. Use /shared/provisional/1.6.0/images.js and declare managed-images-v1. The fixed FLUX.2 Klein 4B profile produces phone-sized 512×512 images with a $5 per-room exposure ceiling. Have the operator reserve a premium profile for the existing creator and slug, then use premium upload/2 (ordinary upload/1 rejects this service). Return the accepted creator/family/submission/digest receipt for separate image grant activation. Read [Image room dynamics](/create/docs/docs/premium/IMAGE_ROOM_DYNAMICS.md) for recovery, deadlines, host skip, private awards and opt-in reminders. The guide contains exact calls, error behavior, private-media semantics and unsupported features. Do not invent API methods or describe packaged sample art as a live generated image.

Room recovery and rematches

Read /create/docs/docs/premium/ROOM_DYNAMICS.md for SDK 1.5.0. Discover same-browser exact-edition seats with listResumableRooms(), keep a seat with room.detach(), revoke it with room.leave(), and use host-only room.restart() from final for the next game under the same code. Reconcile gameIndex and clear app game caches on change. Declare counts and leaderboard configurations explicitly; a premium grant does not modify an existing immutable submission. General ephemeral frame storage is not available.

Shared agent tools and testing

The creator page exposes a shared tool catalog through ordinary browser controls, WebMCP when supported, and Streamable HTTP MCP at /mcp. Read /create/docs/docs/agents/AGENT_INTERFACES.md for setup, supported protocol versions and authentication. These tools reuse the existing upload service; bulk file streaming and registration retain their current endpoints and helper.

Before any model tests, read /create/docs/docs/agents/TESTING.md. Use modelTest=1 on hosted previews or X-Riff-Execution-Mode: test on direct managed-image mutations to block model dispatch. test_agent=1 alone only labels analytics. No-dispatch returns a refusal, not simulated artwork. Use local fixtures for successful-image tests and a separately approved bounded grant for live generation.

Before requesting hosted testing, declare every required service and follow the [testing-readiness handoff](/create/docs/docs/agents/TESTING.md). Include the exact revision/digest and bounded expected usage; acceptance is not model-spending activation.

Expired private links

A private claim link lasts up to 7 days; opening it establishes a 20-minute cookie for the sign-in round trip. If the cookie expires, reopen the original private link. If the link expires while the creator key is still valid and unclaimed, rerun the same batch with its original private key and journal: the helper renews the claim link without creating new apps or resending an accepted/unknown email. Give the replacement link privately to the owner. If the key is expired, revoked or already claimed, use the existing owner dashboard or contact the platform for recovery; never register a duplicate creator.

Analytics before publication and approval

Before publishing a preview or approving any new app/version, follow [Analytics publish and approval checks](docs/analytics/PUBLISH_APPROVAL_CHECKS.md). Review App Stats and Platform Health in the simulator, verify the exact release/submission identity and a marked server receipt (test_agent=1), and check meaningful starts, intermediate milestones and final completion separately. Unknown or local-only telemetry is not verified hosted analytics. Uploaded foreground time and historical family aggregates cannot prove semantic coverage or a new revision's correctness. Record gaps explicitly; preserve existing ownership and approval boundaries.

Temporary publication-count pause — October 14, 2026

During agent iteration the operator can pause submission-count admission with RIFF_SUBMISSION_CAPS_PAUSED=true. Read /api/contributions/submission-policy for the current deployment's effective state. The pause applies to upload/1 creator invitation quotas and standard global submission counts, and upload/2 per-family rolling publication counts. Stored quotas remain recorded and become effective again when the flag is removed or set to false. It does not waive creator ownership/delegation, key expiry, exact parent/retry semantics, public-content consent, package/file limits, retained-byte budgets, registration throttles, concurrent upload/session rates, premium entitlement or model spending. Never create another creator or strip declarations to evade a remaining gate. Publication stays provisional; Main approval remains separate.