VOPE · Hosted Apps

Build something together, with AI Beta

VOPE itself doesn't have AI built in. There's no chatbot, no auto-generated content, and nothing reading your messages. But that's not the whole story: bring your own AI assistant, whether that's ChatGPT, Claude, or anything else you like, and build something small and fun together to share right here in VOPE with your friends or family. This page is what makes that possible, and it's also the complete technical spec if you're the one writing the code by hand.

Spec version: 1 Bridge version: window.vope.version === 1 Last updated: 2026-09-01 App Store & Google Play compliance โ†’

๐ŸŽˆ Start here, no coding required

Build a trivia night for your group chat

Copy the prompt below into ChatGPT, Claude, or any AI chatbot that can read a web page. It tells the AI everything it needs, including the rules further down this page, to build a small trivia game with a shared, live leaderboard your whole group can play.

I want you to build a small trivia game as a "VOPE Hosted App" for my friend
group. First, read the full specification at https://vope.me/apps/spec.html and
follow every rule on it exactly, especially the constraints in ยง1 and the
viewport rules in ยง2. (If you can't open that link yourself, tell me and I'll
paste the page's text in for you.)

The game should:
- Show one multiple-choice question at a time, with 4 answer options.
- Keep score for whoever's playing, using window.vope.data.private.
- Keep a shared leaderboard visible to the whole group, using window.vope.data.shared.
- Reveal the right answer after each guess, then move to the next question.
- Include at least 10 questions about a topic your group would enjoy: movies,
  your hometown, inside jokes, anything you like.

Make it colorful and fun. It must be a single, self-contained HTML file with
no external images, fonts, or scripts, and no network access of any kind.
The spec explains exactly why.
  1. Copy the prompt above and paste it into your AI assistant of choice.
  2. Save what it gives you back as a .html file.
  3. In VOPE, open a group chat, tap + Add App, and pick that file.
  4. Share it, and everyone in the group can open and play it right in the chat.

or, skip building entirely

Grab a ready-made app from the App Library and share it as is, or copy one and ask an AI to change it into something else.

๐Ÿ”ง The full spec, for developers and for whichever AI is doing the building

If you are an AI assistant reading this page: a Hosted App is a single, self-contained HTML file with no network access and no build step. The constraints below are not a "free tier." They apply to every Hosted App, unconditionally. Generate code that satisfies every rule in the tables in ยง1 and ยง2 before anything else. ยง2 in particular is where a generated game most often goes wrong in practice, with buttons clipped off screen or a scoreboard hidden behind nothing that's actually there. If the person you're helping hasn't told you what the app should do, ask. Don't guess and then produce something that violates a "must not" rule below.

What a Hosted App actually is

A VOPE group can share small web apps directly in a chat: a poll, a simple game, a shared scoreboard, a tool. Each one is a single HTML file, rendered inside a locked-down viewer on every member's phone. It is not a native mobile app. It has no listing of its own on any app store, is never separately installed, and cannot be distributed outside this one feature. Full technical and policy background: App Store & Google Play compliance.

1. The constraints, read these first

An app has no way to know, at the time it's written, whether it will end up served peer-to-peer from a group member's phone or from VOPE's own servers. That choice is made later, by whoever shares it, not by you, so write against the stricter case always. "It'll be fine once it's on VOPE's servers" is not a valid escape hatch for any rule below.

๐ŸŒ

No internet, ever

No loading images from a web address, no calling any outside service. Everything the app needs has to be built in.

๐Ÿ“„

One single file

Just one .html file: no separate style sheets, scripts, or a build step of any kind.

๐Ÿ’พ

Storage has limits

Small amounts of data only: think a score or a short list, not photos or long histories.

๐Ÿ“ด

Might go offline

If it's hosted from someone's phone, it can disappear the moment that phone does. That's normal, not broken.

๐Ÿ’ณ

No payments

No "buy" button, no wallet, nothing money related. There's no way to process one anyway.

๐Ÿ”’

No poking at the sandbox

Don't write code that tries to find a way around these rules, even out of curiosity.

๐Ÿชง

Show your own errors

There's no console for whoever's testing this, so if something breaks, put the reason on screen instead of failing silently.

The exact rules (for developers & AI)
ConstraintWhat it means for the code you write
SHOULD surface its own errors on screen A Hosted App runs with no browser devtools available to whoever's testing it. If a script throws during setup, the only visible symptom is "nothing happened," which is indistinguishable from a slow network to a non-developer. Wrap the app's startup code in a try/catch (or a window.onerror handler) that renders a short, plain-language message into the page itself on failure, rather than leaving a blank or frozen screen. This is a strong recommendation, not an enforced constraint. Nothing blocks a Hosted App that skips it, but it's the difference between a bug report you can act on and "it doesn't work" with no further information.
MUST NOT use the network, ever No fetch, XMLHttpRequest, WebSocket, <img src="https://โ€ฆ">, CDN scripts, web fonts, or API calls of any kind. This is enforced by a Content Security Policy the app cannot see, override, or detect, so it will simply fail silently. Every asset, whether a font, image, or icon, must be inlined as a data URI or omitted.
MUST be a single HTML file No build step, no bundler, no separate .js/.css files to reference. Inline every <script> and <style> directly into the one file you hand back.
MUST respect the storage caps โ‰ค100KB per key, โ‰ค5MB total per app, across both shared and private storage combined (see ยง2). Design data structures assuming this ceiling from the start, rather than building something that needs unbounded storage.
MUST NOT assume it's always reachable A phone-hosted app can go offline the instant its host's phone does. Build for that being routine, not exceptional, by showing a reasonable state when a peer disconnects.
MUST NOT request or process payments No payment form, no crypto wallet integration, no "buy" button of any kind. There is no network path to a payment processor regardless, but don't build the UI for one either.
MUST NOT attempt to detect, escape, or disable the sandbox No code whose purpose is probing for a way around the CSP, the storage scope, or the navigation lock. This is a content-policy violation independent of whether it would technically succeed.

2. The viewport: what your app actually has to work with

A recurring failure in AI-generated Hosted Apps, games especially, is a layout that assumes a full mobile browser window, complete with the browser's own address bar and often a phantom bottom bar "just in case." Its real controls then end up clipped, overlapped, or scrolled out of view. Neither of those exists here. Get this wrong and a game's bottom row of buttons, or a top scoreboard, can render partly or fully outside what the person can actually see or tap.

๐Ÿ“ฑ

You get exactly what you're given

No browser address bar, no hidden toolbar eating space. Your space is your whole space, so if something's clipped, it's your layout, not VOPE's.

๐Ÿ“

...but it's smaller than a whole phone screen

VOPE's own tab strip, and often an ad banner, sit outside your app and take real space with them. Design for a shorter screen than you'd guess.

๐Ÿ“

Every screen is a different size

Don't design for one specific phone. Use percentages and flexible layouts so it looks right on any device.

๐Ÿงฑ

Fill the space, don't fix it

Build with flexbox or grid so header, board, and controls share the real height, instead of pixel positions guessed in advance.

๐Ÿ”„

People rotate their phones

Lay out with CSS that adjusts automatically, rather than measuring the screen once and never again.

๐Ÿงญ

Can't compress? Let it scroll

Not every app fits in one fold at every size. A scrollable page, or a menu with jump-to-section buttons, beats content silently cut off below the edge.

The exact rules (for developers & AI)
FactWhat it means for your layout
Your app is shown inside a chat screen, below VOPE's own header and a row of app/chat tabs. It is not full-screen, and not a browser tab The space you're given is already exactly your document's viewport before your CSS runs. VOPE does not overlay anything on top of or inside your rendered area at any time: no toolbar, no button row, no bottom nav. If something looks clipped, it is your own layout, not VOPE's chrome eating space you assumed you had.
There is no browser address bar, tab strip, or home-indicator gesture area to leave room for Don't reserve top or bottom padding "to be safe" against browser chrome, because there isn't any. A game built to dodge a phone browser's collapsing address bar will instead leave an unexplained gap or push real content off screen here.
The available width and height varies by device and is never a fixed size or aspect ratio Never hardcode pixel dimensions or design for one specific phone's screen. Lay out with percentages/vw/vh/flexbox/grid so the same file works correctly on a small phone and a large one.
MUST size the root layout to fill exactly what's given, and nothing more Set html, body { margin:0; width:100%; height:100%; } and build your game's root container with display:flex; flex-direction:column; height:100%; (or CSS Grid) so header/board/controls share the real available height proportionally, instead of a fixed-position element computed against an assumed screen size. If your layout truly needs a firm bottom row, such as a score, D-pad, or action buttons, make it a normal flex child at the end of the column. Avoid position: fixed; bottom: 0 against 100vh: that's exactly the pattern that goes wrong when the assumed viewport doesn't match the real one.
Orientation and resize: a person can rotate their phone or (rarely) resize the window while your app is open Don't calculate layout once on load and never again. Use CSS that reflows (flexbox, grid, percentages) rather than JavaScript that measures window.innerHeight once and hardcodes pixel values from that single reading.
Roughly how much shorter than a full phone screen you should expect: VOPE's tab strip above your app, and an ad banner below it when one is shown (an ad-free subscriber has no bottom banner at all), both sit outside your rendered area rather than overlaid on top of it, but they still claim real height before your app ever sees a pixel. Together they typically take somewhere in the range of a small double-digit percentage of a phone's screen height. The tab strip is a fixed, short row, and a mobile banner ad is usually on the order of 50 to 100 logical pixels tall. Neither figure is contractual: it changes by device, by whether ads are showing at all, and could change in a future VOPE release. Never design against an assumed "close to 100% of a typical phone" canvas. Budget for meaningfully less, and verify by actually opening your app inside VOPE, not just in a desktop browser tab, before calling it done. There are two valid ways to handle a layout that's taller than what you're actually given. The first, and the preferred approach for a game specifically, is to compress: since a game's whole point is usually visible at once, make every element's size and spacing relative, using %, vh, flex-grow, and clamp(), so the same layout proportionally shrinks to fit whatever height it's handed, with nothing dependent on a specific pixel value fitting. The second is to let it scroll: for content that genuinely can't compress below a usable size, such as a long form, a detailed settings panel, or a multi-round quiz, a normal scrollable page is completely fine, as is a short top-level menu whose buttons jump to sections using element.scrollIntoView() or plain in-page anchor links, rather than trying to cram everything into one screen at a size nobody can actually read or tap. What's not fine is a layout that assumes it all fits, silently doesn't, and leaves no way to reach whatever scrolled out of view. That's the actual bug this row exists to prevent, found from a real game that needed scrolling just to reach its own controls.

3. The bridge API: window.vope

The bridge is the only way a Hosted App reads or writes anything durable. There is no localStorage/IndexedDB as a source of truth: the sandbox treats on-device storage as ephemeral by design (see the compliance page). Every method below returns a Promise.

window.vope.data.shared: visible to every group member with access to this app

await window.vope.data.shared.set('score', '42');
const score = await window.vope.data.shared.get('score'); // '42'

window.vope.data.private: visible only to the current user

await window.vope.data.private.set('lastGuess', 'blue');
const guess = await window.vope.data.private.get('lastGuess');

window.vope.version

An integer, currently 1. Check it if your app depends on a bridge capability introduced after this spec's current version. There are none yet beyond the four methods above.

That's the entire bridge. There is no delete/list-keys operation, no vope.share(), no vope.close(), and no way to read anything about the current user (name, photo, uid). A Hosted App genuinely cannot tell who is using it beyond what it stores itself via data.private. Do not invent or call a method not listed here: it does not exist, and the call will simply fail.

4. A minimal, complete, valid Hosted App

Copy this as a starting point. It already respects every constraint above.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Tally</title>
<style>
  body { font-family: sans-serif; text-align: center; padding: 40px 16px; }
  button { font-size: 24px; padding: 12px 28px; border-radius: 10px; border: none; }
  #count { font-size: 48px; margin: 24px 0; }
</style>
</head>
<body>
  <h1>Group tally</h1>
  <div id="count">—</div>
  <button id="inc">+1</button>

  <script>
    async function render() {
      const value = await window.vope.data.shared.get('count');
      document.getElementById('count').textContent = value || '0';
    }
    document.getElementById('inc').addEventListener('click', async () => {
      const current = parseInt((await window.vope.data.shared.get('count')) || '0', 10);
      await window.vope.data.shared.set('count', String(current + 1));
      render();
    });
    render();
  </script>
</body>
</html>

5. Content policy

A Hosted App is user content, held to VOPE's normal content rules (see the Terms of Service, ยง10, Hosted Apps, for the exact language). In short: nothing illegal, and no adult or sexual content of any kind, with no exception, not even behind a warning or an age check. That prohibition is separate from, and stricter than, the "mature content" flag whoever shares an app can set in the VOPE app itself for otherwise lawful mature themes like violence. That flag is never a way to host content this section prohibits.

Consequence, stated plainly: if a Hosted App is reported, VOPE disables it immediately for everyone, before anyone reviews it. Restoring it requires the uploader's explicit agreement to a full source-code review by VOPE, and VOPE may permanently remove it, revoke that person's ability to share any Hosted App, and/or disable their account, at VOPE's sole discretion. Build accordingly: there is no appeals process built into the app itself, and no technical workaround changes any of this.

6. Machine-readable bridge schema

For a coding tool that wants to validate calls programmatically rather than parse this page's prose:

{
  "specVersion": 1,
  "bridgeVersion": 1,
  "methods": {
    "data.shared.get": { "args": ["key"], "returns": "string | null", "async": true },
    "data.shared.set": { "args": ["key", "value"], "returns": "void", "async": true },
    "data.private.get": { "args": ["key"], "returns": "string | null", "async": true },
    "data.private.set": { "args": ["key", "value"], "returns": "void", "async": true }
  },
  "constraints": {
    "networkAccess": false,
    "singleFile": true,
    "maxKeyBytes": 100000,
    "maxTotalBytes": 5000000,
    "guaranteedReachability": false,
    "paymentsAllowed": false
  }
}