Hosting
Velven hosts your space: publish a folder and, once it passes a safety check, it is live at its own velven.io address, played in the frame on its Velven page with the SDK added. A space already live on one of 7 other hosts can be linked instead, and moved onto Velven's hosting whenever you like.
Publish on Velven
Publish the folder your build writes, with an index.html at its top. There are 3 ways in, and they make the same versions: the Velven CLI, Upload files on the add page (a folder, a zip, one HTML file or pasted HTML), and the publish tool of Velven's MCP server for an assistant in a chat.
npx @velven/cli loginnpx @velven/cli publish ./dist # a private preview; the link is printednpx @velven/cli publish ./dist --prod # live once Velven's safety check passes- Only files Velven does not already have are uploaded, so publishing again after a small change is quick.
- A plain publish makes a preview: a private link, nothing public changes.
--prodsends the version to the safety check. When it passes, the version replaces the live one and the space is on Velven. The first time, that makes the listing.- Each space gets its own address, 8 random letters and digits on velven.io, such as
k3xq7vtm.velven.io. It is served only inside the frame on the space's Velven page.
Note: No account yet? A publish without signing in makes an unlisted page you can claim later. See Publish without an account.
velven.json
The listing and the hosting settings come from velven.json in the folder you publish. Velven never guesses them. The CLI asks for a missing required field in a terminal and saves your answer into the file; anywhere else it stops and names each missing field. Flags such as --title override the file for one run. The file is never part of a version: Velven does not upload or serve it, nor a velven.json in any folder below.
{ "title": "Star Hop", "type": "game", "devices": ["desktop", "mobile"], "description": "Hop between stars before the light runs out.", "engine": "three.js", "start": { "click": "Play" }, "boards": [{ "key": "main", "trust": "client", "display": "both" }], "stats": [{ "key": "stars", "label": "Stars" }], "achievements": [{ "key": "first-hop", "title": "First hop", "description": "Reach another star" }]}Listing
titleUp to 80 characters- Required. The name people see on Velven.
typegame,world,toolorwonder- Required. What kind of space it is.
devicesA list ofdesktop,mobile,vr- Required. What it works on.
descriptionUp to 160 characters- One line on what the space is.
engineAn engine's value- What it is built with. Allowed values.
ai_toolsA list of tool values- The AI tools it was made with.
modelsA list of model values- The models it was made with.
how_madeUp to 2,000 characters- How it was made, the prompt included if you like.
source_urlA web address- Its source code, if public.
The listing fields become the space's when you publish to production. A preview never changes the public listing.
Hosting
entryA path in the folderDefaultindex.html- The page the frame opens.
spatrueorfalseDefaultfalse- Serve the entry page for any path with no file and no extension, for client-side routing.
sdktrueorfalseDefaulttruefalsestops Velven adding the SDK's script tag. Set it when you bundle@velven/sdkyourself.toaststrueorfalseDefaulttruefalsestops Velven drawing its toast for an achievement unlock, when your space draws its own.start{ "click": "Play" },{ "click": [x, y] }or{ "key": "Space" }- How to get past a start screen, for the safety check. A click on the button or link with that text, a click at a point in a 1280 by 720 frame, or a key by its name, such as
EnterorArrowUp.
Leaderboards, achievements and stats
boardsA list of boards- The same shape as the page's boards block. Board fields.
statsA list of stats- Named counters per player. Achievements and stats.
achievementsA list of achievements- An
iconcan be a file in the folder, by its path. Achievements and stats.
They apply when the version goes live, never on a preview. A version that names boards makes its list the space's boards, and one that names stats or achievements makes those two lists the space's: a key you drop is withdrawn with its scores and unlocks kept. A version that names none of them, such as one published with the MCP server's publish tool, leaves the space's as they are. Rolling back brings the older version's back. A preview's scores and unlocks go to the sandbox against the live space's declarations.
Written by the CLI
spaceA space's slug- Written by the first publish. Later publishes update that space; publishing to a space you do not own is refused.
claimA claim token- Written by a publish without an account. Publishing again updates the same unlisted page; claiming replaces it with
space.
A .velvenignore file in the folder leaves files out: one pattern a line, with * and ? as wildcards, a doubled * for any depth of folders, a trailing / for folders and # for comments. Dotfiles, node_modules and every velven.json, in any folder and any case, are always left out, whichever way you publish. The CLI also leaves out a name that ends in a dot or a space or holds : or ~ (one velven dev would not serve), and names each. A link to a file or folder inside the folder is followed, a folder link's files published under the link's name; a link outside the folder, to one of those, or back up to a folder it sits in is left out, and the CLI names it.
Limits
| Signed in | Without an account | |
|---|---|---|
| One version | 100 MB and 2,000 files | 50 MB and 1,000 files |
| Publishes | 30 an hour and 200 a day per account | 10 an hour and 30 a day per network address |
| Spaces | No limit | Each unlisted page is deleted 7 days after its first publish unless claimed |
| Versions kept | The live one and the last 20 | The live one and the last 20 |
| A preview link | 24 hours | No previews: each publish is checked, then live |
- One version
- Signed in
- 100 MB and 2,000 files
- Without an account
- 50 MB and 1,000 files
- Publishes
- Signed in
- 30 an hour and 200 a day per account
- Without an account
- 10 an hour and 30 a day per network address
- Spaces
- Signed in
- No limit
- Without an account
- Each unlisted page is deleted 7 days after its first publish unless claimed
- Versions kept
- Signed in
- The live one and the last 20
- Without an account
- The live one and the last 20
- A preview link
- Signed in
- 24 hours
- Without an account
- No previews: each publish is checked, then live
A version over the size limit is refused with its largest files named, so you know what to shrink or leave out. A publish over the rate limit answers rate_limited with the wait. Showing a hidden space runs the safety check again for a version still waiting on one, and each such check counts toward the hour as a publish does; past it the version keeps waiting until you publish again.
The safety check
Every version is checked before it goes live. Velven opens it in a browser as a player would, records a short take, and a model judges the take against the content policy: sexual content, scams, sign-in, payment or wallet forms, and gambling. The version's address, and every address its entry page names, may also be looked up in a list of known malware and phishing. The check usually takes a few minutes.
| Outcome | What happens | What you do |
|---|---|---|
| Live | The version replaces the live one. The take becomes the space's clip and thumbnail. | Nothing. |
| Refused | The version stays off, with the reason, and the live version keeps serving. | Fix it and publish again, or press Ask for review. |
| Could not judge | The take showed a black screen, a loading screen or a start screen it never got past. The version stays off, with what the take showed. | Add a start hint to velven.json and publish again, or press Ask for review. |
- Outcome
- Live
- What happens
- The version replaces the live one. The take becomes the space's clip and thumbnail.
- What you do
- Nothing.
- Outcome
- Refused
- What happens
- The version stays off, with the reason, and the live version keeps serving.
- What you do
- Fix it and publish again, or press Ask for review.
- Outcome
- Could not judge
- What happens
- The take showed a black screen, a loading screen or a start screen it never got past. The version stays off, with what the take showed.
- What you do
- Add a
starthint tovelven.jsonand publish again, or press Ask for review.
Follow the check on the space's Files tab, from the link the CLI prints, or wait in the terminal with velven publish --prod --wait, which exits 6 when the version is refused or could not be judged. Ask for review is on the Files tab: a person at Velven looks at the version, once per version, while no newer version is live.
Note: A newer version sent to the check while an older one waits replaces the older one, which never goes live.
Previews
A plain velven publish, or Upload a preview on the space's Files tab or the add page, makes a preview: the version, played in Velven's page at a private link on velven.ai that works for 24 hours. Anyone signed in to Velven who holds the link can open it, so a friend can test a room with you. Nothing public changes, and the preview is not checked.
Everything a preview writes, scores, saves, achievements, stats, content and rooms, goes to the space's sandbox, never the live space. velven reset clears the sandbox, and it clears itself 30 days after the last write.
To put a preview live, run velven publish --prod from the same folder, or press Publish beside it on the Files tab. Either sends it to the safety check first.
Versions and rollback
Every publish is a new numbered version. Velven keeps the live one and the last 20; older versions, and files no kept version uses, are deleted.
velven versions # * marks the live onevelven rollback 3 # version 3 goes live again, at onceA rollback goes only to a version that was live before, since it has already passed the check. The Files tab lists the same versions with their status; Make live beside a version that was live before rolls back to it.
Publish without an account
velven publish before you sign in, Upload files on the add page signed out, and the MCP publish tool without a sign-in all make an unlisted page: a real space page on Velven, live once it passes the check, with every SDK feature (though its achievements add no points), but on no list, search, shelf or sitemap. It shows Unclaimed and a Claim button. By publishing you agree to Velven's Terms.
- The first publish answers with the page's address and a claim token. The CLI saves the token in
velven.jsonas"claim", so publishing again from the folder updates the same page, checked each time. - The page and everything on it are deleted 7 days after its first publish unless you claim it. Publishing again does not add time.
--prodneeds you to sign in: without an account, every publish is checked and then live on the unlisted page.
To claim it, open the claim link, https://velven.ai/claim/<token>, and sign in; or run velven login and publish again from the folder. The page becomes yours and goes on Velven, and "claim" in velven.json becomes "space".
Warning: The claim token is the key to the page: anyone who has it can update or claim it. Keep velven.json out of a public repository until you have claimed the page, or delete the claim line after claiming.
How Velven serves your files
/serves the entry page and/dir/servesdir/index.html. A path with no file is a 404, or the entry page whenspaistrueand the path has no extension.- Types come from the file's extension;
.wasmis served asapplication/wasm. A compressed build file such asgame.wasm.brordata.js.gzis served as the file inside it, with itsContent-Encoding, so a Unity or Godot web export works as it is. - Velven adds the SDK's script tag, which loads
/sdk/v1.jsfrom Velven, before the entry page's</head>, unlesssdkisfalseor the page already loads a script from/sdk/v1.js. - The entry page is never cached. Every other file is revalidated by its hash on each load, so a new version reaches players on their next load and an unchanged file costs no download.
- Each space has its own origin, so its
localStorageand IndexedDB are its own.Velven.datasaves belong to the space whatever its address. - Set cookies for the space's own host only, never for all of velven.io: another space could read or overwrite a cookie set that wide.
Velven.datais the way to keep a player's progress. - A visitor who opens the velven.io address directly is sent to the space's Velven page. Only velven.ai may frame it, and search engines are told not to index it.
Note: Velven does not send cross-origin isolation headers, so a build that needs SharedArrayBuffer, such as a threaded Godot 4 or Unity export, does not run. Export it without threads.
Move a linked space onto Velven
A space you listed from another host can move onto Velven's hosting and keep its slug, plays, likes, boards, scores, saves and page text. Put its slug in velven.json as "space" and run velven publish --prod, or press Host it on Velven on its edit page and upload the files. The space moves when that version passes the check; until then the old address keeps playing.
To move back to a live URL, enter it under Move to a new address on the same tab; the new page needs the proof tag. A space Velven listed and nobody has claimed cannot move.
Note: The space's origin changes with a move, so anything it kept in localStorage starts empty at the new address. Velven.data saves move with it.
Link a live URL
A space already live elsewhere can be listed as it is: it stays where it is hosted, and Velven plays it in a frame and counts its plays. It can be listed when it is on a host Velven knows, names its creator in a proof tag, and lets velven.ai frame it. Velven lists spaces on these hosts and refuses any other URL with a 422. The host is read from the listed URL's address and response headers, after redirects.
| Host | Recognised by |
|---|---|
| ChatGPT | A chatgpt.site address. The site must be published with “Who has access” set to “Anyone on the Internet”. A site only its owner can open answers 401, and Velven cannot check it. |
| Vercel | A vercel.app address, or a custom domain whose response headers say Vercel. |
| Netlify | A netlify.app address, or a custom domain whose response headers say Netlify. |
| GitHub Pages | A github.io address or a custom domain served by GitHub Pages. A repository page on github.com is not a Pages site and is refused. |
| Cloudflare | A workers.dev or pages.dev address only; a custom domain on Cloudflare is not recognised yet, since the server: cloudflare header proves nothing. |
| Replit | A replit.app address only. A custom domain on a Replit app is not recognised, since Replit adds no header of its own; a replit.dev address is the workspace's development preview, not a deployment. |
| Firebase | A web.app or firebaseapp.com address only. A custom domain on Firebase Hosting is not recognised, since Firebase adds no header of its own. |
- Host
- ChatGPT
- Recognised by
- A chatgpt.site address. The site must be published with “Who has access” set to “Anyone on the Internet”. A site only its owner can open answers 401, and Velven cannot check it.
- Host
- Vercel
- Recognised by
- A vercel.app address, or a custom domain whose response headers say Vercel.
- Host
- Netlify
- Recognised by
- A netlify.app address, or a custom domain whose response headers say Netlify.
- Host
- GitHub Pages
- Recognised by
- A github.io address or a custom domain served by GitHub Pages. A repository page on github.com is not a Pages site and is refused.
- Host
- Cloudflare
- Recognised by
- A workers.dev or pages.dev address only; a custom domain on Cloudflare is not recognised yet, since the
server: cloudflareheader proves nothing.
- Host
- Replit
- Recognised by
- A replit.app address only. A custom domain on a Replit app is not recognised, since Replit adds no header of its own; a replit.dev address is the workspace's development preview, not a deployment.
- Host
- Firebase
- Recognised by
- A web.app or firebaseapp.com address only. A custom domain on Firebase Hosting is not recognised, since Firebase adds no header of its own.
Note: Size your space to the viewport, not to a fixed ratio. On a desktop it fills the page's width at the window's height; on a phone it takes the whole screen.
The proof tag
Only a verified creator can list a space, and a space Velven listed itself is claimed the same way. The proof is one tag in the page's <head> naming your Velven handle. Velven looks for it on the live page when the space is listed or claimed. A space published on Velven needs no tag: the upload is the proof.
Add the tag below inside the <head> of the page at the listed URL.
<meta name="velven" content="@handle">| Host | After adding the tag |
|---|---|
| ChatGPT | In ChatGPT press Share, set “Who has access” to “Anyone on the Internet”, then publish again. |
| Vercel | Deploy the change. |
| Netlify | Deploy the change. |
| GitHub Pages | Push, and wait for Pages to rebuild. |
| Cloudflare | Deploy the change. |
| Replit | Publish again on Replit. |
| Firebase | Deploy again with firebase deploy. |
- Host
- ChatGPT
- After adding the tag
- In ChatGPT press Share, set “Who has access” to “Anyone on the Internet”, then publish again.
- Host
- Vercel
- After adding the tag
- Deploy the change.
- Host
- Netlify
- After adding the tag
- Deploy the change.
- Host
- GitHub Pages
- After adding the tag
- Push, and wait for Pages to rebuild.
- Host
- Cloudflare
- After adding the tag
- Deploy the change.
- Host
- Replit
- After adding the tag
- Publish again on Replit.
- Host
- Firebase
- After adding the tag
- Deploy again with firebase deploy.
Deploy first and wait for the host's cache. A check that comes too early answers 409 unverified, with the change still needed. The older proof, a /.well-known/velven file at the site's origin containing @handle, is still accepted.
Allow Velven to frame your page
Every space plays inside the Velven page, with no option to open it in a new tab instead, so its host must let velven.ai frame it. All 7 hosts allow that by default, so most pages need nothing. A page that sends its own headers must not send X-Frame-Options, and its Content-Security-Policy must name velven.ai in frame-ancestors:
Content-Security-Policy: frame-ancestors 'self' https://velven.aiVelven reads the headers when the space is listed, and again on its background check every 6 hours. The agent API refuses a page that blocks framing with 422 unframeable and the change for its host. A listed space whose host starts refusing frames leaves Velven until the header is back; its creator sees the fix on the space's page and its edit page, and can press Check again on either.
ChatGPT
A ChatGPT site sends no headers of its own and always allows framing. Nothing to do.
Vercel
Add this to vercel.json at the project root, merged into any headers list already there. A Next.js app can set the same header in next.config instead.
{ "headers": [ { "source": "/(.*)", "headers": [ { "key": "Content-Security-Policy", "value": "frame-ancestors 'self' https://velven.ai" } ] } ]}Remove any X-Frame-Options header the app sets (a security preset, a middleware).
Deploy the change.
Netlify
Add this to a _headers file in the publish directory, or the same rule under [[headers]] in netlify.toml.
/* Content-Security-Policy: frame-ancestors 'self' https://velven.aiRemove any X-Frame-Options header the app sets (a security preset, a middleware).
Deploy the change.
GitHub Pages
GitHub Pages sets no headers and always allows framing. Nothing to do; a page that refuses framing there is being blocked by something in front of it.
Cloudflare
On Pages, or a Worker that serves static assets, add this to a _headers file in the output (assets) directory. On a Worker whose fetch handler builds the page, set the header on the response there.
/* Content-Security-Policy: frame-ancestors 'self' https://velven.aiRemove any X-Frame-Options header the app sets (a security preset, a middleware).
Deploy the change.
Replit
On a static deployment, add this to the .replit file at the project root. On an Autoscale or Reserved VM deployment, set the header on the response in the server.
[[deployment.responseHeaders]]path = "/*"name = "Content-Security-Policy"value = "frame-ancestors 'self' https://velven.ai"Remove any X-Frame-Options header the app sets (a security preset, a middleware).
Publish again.
Firebase
Add this to firebase.json at the project root, merged into the hosting block and any headers list already there.
{ "hosting": { "headers": [ { "source": "**", "headers": [ { "key": "Content-Security-Policy", "value": "frame-ancestors 'self' https://velven.ai" } ] } ] }}Remove any X-Frame-Options header the app sets (a security preset, a middleware).
Deploy again with firebase deploy.
What Velven checks on a linked space, and when
Velven checks a linked page when it is listed, when it is claimed, every 6 hours in the background, and when you press a button for it:
| When | What |
|---|---|
Listing, on the add page or POST /api/spaces | The host, that the page answers, the proof, the framing headers, the page's boards block, and it may be checked against the malware and phishing list. |
A claim, at /s/{slug}/claim or POST /api/spaces/verify | The proof. |
A move, on the edit page's Settings tab or POST /api/spaces/{slug}/move | The new page as a listing checks it: the host, that it answers with a page and does not ask visitors to sign in, the framing headers, and, on a claimed space, the proof and its boards block; it may also be checked against the malware and phishing list. |
| Every 6 hours, in the background | That the page still answers and allows framing, its boards block, and whether it changed; it may also be checked against the malware and phishing list. A page that changed gets the same safety check as a hosted version, at most once a day, and one judged unsafe is hidden until Velven reviews it. |
| Check again, on the space's page and its edit page while its host refuses frames | The same as the background check, at once. A page that allows framing again goes back on Velven. |
| Check my page now, on the edit page's Leaderboards tab | The boards block, at once. |
- When
- Listing, on the add page or
POST /api/spaces - What
- The host, that the page answers, the proof, the framing headers, the page's boards block, and it may be checked against the malware and phishing list.
- When
- A claim, at
/s/{slug}/claimorPOST /api/spaces/verify - What
- The proof.
- When
- A move, on the edit page's Settings tab or
POST /api/spaces/{slug}/move - What
- The new page as a listing checks it: the host, that it answers with a page and does not ask visitors to sign in, the framing headers, and, on a claimed space, the proof and its boards block; it may also be checked against the malware and phishing list.
- When
- Every 6 hours, in the background
- What
- That the page still answers and allows framing, its boards block, and whether it changed; it may also be checked against the malware and phishing list. A page that changed gets the same safety check as a hosted version, at most once a day, and one judged unsafe is hidden until Velven reviews it.
- When
- Check again, on the space's page and its edit page while its host refuses frames
- What
- The same as the background check, at once. A page that allows framing again goes back on Velven.
- When
- Check my page now, on the edit page's Leaderboards tab
- What
- The boards block, at once.
List a space with an agent has the API calls, and Leaderboards the boards block.
Move a linked space to another host
A space that moves to a new URL keeps its listing: its slug, plays, likes, boards, scores, saves and page text stay. Put the proof tag on the new page and allow framing there, deploy, then enter the new URL under Move to a new address, on the Settings tab of the space's edit page.
An agent moves it with POST /api/spaces/{slug}/move; Move to another host has the call and what to update on the new host, and the REST API every refusal.
Note: A server that posts scores or checks identity tokens needs the move too: a board secret in the new host's environment, and the new origin as the tokens' audience.