Documentation

Everything you need to build apps that live inside posts.

How embedpad turns a sentence into a multiplayer app, the SDK every app gets, the limits, the REST API, and how coins and fee splits work on chain.

Start

Overview

embedpad builds a complete web app from a description, hosts it in a sandbox, and gives you a share link that X renders as a playable card right inside the post.

One fileEvery app is a single self-contained HTML file. No build step, no dependencies to install.
MultiplayerEach app has a shared room: state and presence synced for everyone who opens the post.
Optional coinPair a pump.fun coin. Its website is the app, and creator fees go where you choose.
Start

Quickstart

From an idea to a post people can play, without leaving the browser.

  1. 1
    Describe it
    Type what you want on the home page and press build. The builder streams the app as it writes.
  2. 2
    Iterate
    Ask for changes in plain words. Every version is saved and can be restored. Runtime errors show a one-click Fix it.
  3. 3
    Share the link
    Copy the share link (www.embedpad.app/s/…) and post it on X. The post unfurls into the live app.
  4. 4
    Pair a coin (optional)
    Hit Launch a coin in the builder to create a pump.fun coin paired with the app, in one wallet approval.
the smallest multiplayer app
<!doctype html>
<html>
  <body>
    <button id="btn">0 taps</button>
    <script>
      // The runtime injects window.embedpad before your code runs.
      const btn = document.getElementById('btn')

      embedpad.room.subscribe((state) => {
        btn.textContent = (state.taps ?? 0) + ' taps'
      })

      btn.onclick = () => {
        embedpad.room.set('taps', (embedpad.room.get('taps') ?? 0) + 1)
      }
    </script>
  </body>
</html>
Start

Writing good prompts

The builder already knows the canvas size and the SDK. Spend your words on what makes the app fun.

prompt
A tap-to-flap bird game. Everyone who opens the post shares one global high score with their name on it.
prompt
A 24×24 pixel wall. Pick a color, click to paint, and see everyone else painting live.
prompt
A poll with three options and live animated bars that update as people vote.
prompt
A reaction-time test. Show the fastest 10 times of everyone who played, today only.
  • Say what should be shared (a high score, a canvas, votes) and the builder wires up the room for you.
  • Name the feel: colors, pace, sound. “Arcade, neon, fast” goes a long way.
  • Keep follow-ups small and specific. One change per message gives the cleanest results.
SDK

window.embedpad

The runtime injects a tiny global into every app before your code runs. No imports, no keys.

MemberDescription
embedpad.siteThe app id.
embedpad.playerThis visitor: { id, name, color }.
embedpad.setName(name)Change the display name (max 24 characters).
embedpad.inXtrue when framed (inside a post or any iframe).
embedpad.roomShared key-value state for everyone with the link.
embedpad.playersWho has the app open right now.
window.postware is an alias kept for older apps. New code should use window.embedpad.
SDK

Player

Every visitor gets a random id, a guest name and a color for the session.

player.js
embedpad.player
// → { id: "k2v9x0qa", name: "guest-k2v9", color: "#00ba7c" }

// Let people pick a display name (max 24 characters).
embedpad.setName(input.value)

// true when running inside a post (or any iframe)
if (embedpad.inX) document.body.classList.add('compact')
SDK

Room state

One shared JSON key-value store per app. Writes show up locally right away and reach everyone else within about a second.

room.js
// Wait for the first sync before reading.
const state = await embedpad.room.ready

embedpad.room.get('score')          // one key
embedpad.room.all()                 // a copy of every key

await embedpad.room.set('score', 42)       // any JSON value, up to 8 KB
await embedpad.room.remove('score')        // same as set(key, null)

// Called right away with the current state, then on every change.
const stop = embedpad.room.subscribe((state, changedKeys) => {
  if (changedKeys.includes('score')) draw(state.score)
})
stop() // unsubscribe
MethodReturnsNotes
room.readyPromise<state>Resolves after the first sync.
room.get(key)value | undefinedReads the local copy, no network.
room.all()objectA shallow copy of every key.
room.set(key, value)PromiseOptimistic. null deletes the key.
room.remove(key)PromiseSame as set(key, null).
room.subscribe(fn)() => voidfn(state, changedKeys). Runs once immediately.
Last write wins, per key
Two people writing the same key at once means one overwrites the other. Give each person their own key ('score:' + player.id) and combine them when you render.
SDK

Presence

Know who else is here. A visitor counts as present while they've synced in the last 20 seconds.

presence.js
embedpad.players.subscribe((people) => {
  counter.textContent = people.length + ' here now'
  avatars.innerHTML = people
    .map((p) => '<span style="background:' + p.color + '">' + p.name[0] + '</span>')
    .join('')
})

embedpad.players.list() // last known list, no network call

Presence is only sent while something is subscribed, and never from preview thumbnails, so gallery views don't inflate counts.

SDK

Recipes

Patterns that scale to lots of players without people overwriting each other.

// One key per player avoids two people overwriting the same list.
function submit(score) {
  const key = 'score:' + embedpad.player.id
  const best = embedpad.room.get(key)?.score ?? 0
  if (score > best) {
    embedpad.room.set(key, { name: embedpad.player.name, score, at: Date.now() })
  }
}

embedpad.room.subscribe((state) => {
  const top = Object.entries(state)
    .filter(([k]) => k.startsWith('score:'))
    .map(([, v]) => v)
    .sort((a, b) => b.score - a.score)
    .slice(0, 10)
  render(top)
})
storage.js
// localStorage works even inside the sandbox. If the browser blocks it,
// the runtime swaps in an in-memory copy so your code never throws.
localStorage.setItem('muted', '1')

// Per-device settings → localStorage
// Anything other people should see → embedpad.room
SDK

Limits

Generous for games, tight enough to keep every room fast.

LimitValue
Key length1–80 characters
Value size8 KB of JSON
Keys per room5,000
Writes per visitor40 per 10 s, per app
Sync interval~0.9 s subscribed · 4 s idle · paused in hidden tabs
Presence window20 s · up to 50 listed
Builds30 per hour per visitor
Platform

Playing inside X

X renders a web page inside a post when the link carries a player card. Your share page sets it for you.

GET /s/<id>, what X reads
<meta name="twitter:card" content="player">
<meta name="twitter:player" content="https://www.embedpad.app/p/k3n7q2xw9p">
<meta name="twitter:player:width" content="480">
<meta name="twitter:player:height" content="480">
<meta name="twitter:image" content="https://www.embedpad.app/api/card/k3n7q2xw9p">
URLWhat it is
/s/<id>Share page. Post this link. Humans see the app page, X sees the card.
/p/<id>The player X frames at 480×480. Your HTML plus the runtime.
/api/card/<id>Preview image shown before the player loads.
Design for a square
The player is 480×480 inside the post. Keep important UI clear of the bottom-right corner, where the badge sits.

Player pages are served with a sandbox allow-scripts CSP, which gives every app an opaque origin. Apps can't read cookies, your account or this site, and can only be framed by X and embedpad.

Each app opens with a short embedpad splash (tap to skip) showing the title and paired ticker, and keeps a small badge in the bottom-right that links to the app page and its coin.

Platform

Models

Pick the model that writes your app. You can switch between follow-ups.

ModelGood at
Claude Sonnet 5.5Best all-rounder
GPT-5.6 SolStrong at game logic
Kimi K2.7 CodeFast coder
Gemini 3.8 FlashQuickest
GLM 5.3Playful visuals
Coins

Pairing a coin

A real pump.fun coin, created from your own wallet and permanently linked to your app.

  1. 1
    Name it
    Name, ticker, description and image. The default image is coin art with your ticker.
  2. 2
    Choose the fee split
    All creator fees to you, or split them between up to 10 wallets.
  3. 3
    Optional dev buy
    Buy in the same transaction as the launch, so nobody can buy before you.
  4. 4
    Approve once
    One wallet approval signs everything. The coin’s website is your app, and the ticker shows on the app badge.
Launch costs about 0.022 SOL in network rent plus any dev buy. embedpad never holds your keys; your wallet signs every transaction, and the server checks each one matches what you approved.
Coins

Fee splits

Creator fees route through pump.fun's native fee sharing, so payouts happen on chain without us.

split
[
  { "address": "YourWa11et…", "bps": 7000 },   // 70%
  { "address": "Co11abWa11et…", "bps": 2000 }, // 20%
  { "address": "Charity…", "bps": 1000 }       // 10%
]
// Up to 10 wallets. bps must add up to exactly 10,000.
Splits are permanent
pump.fun revokes the admin once a split is set. You can't edit, add or remove wallets later.
Coins

Token pages

Every paired coin gets a page at /coin/<mint> with a live chart, trades, and buy/sell.

Trades are built on the server and signed in your wallet. Coins on the bonding curve trade on pump.fun directly; graduated coins route through Jupiter. Each transaction is checked against an allowlist of programs before it's sent.

Reference

Room API

The same endpoints the SDK uses. Public, CORS-enabled, no key.

GET/api/room/:id?since=:revstate & presence
POST/api/room/:idwrite one key
# Full state on the first call, then only changes after a given rev
curl "https://www.embedpad.app/api/room/k3n7q2xw9p?since=0"

# Write one key (any JSON value, up to 8 KB)
curl -X POST "https://www.embedpad.app/api/room/k3n7q2xw9p" \
  -H "content-type: application/json" \
  -d '{"key":"score","value":42}'

Response

200 OK
{
  "rev": 531,
  "changes": {
    "score": 42,
    "p:4,2": "#ffd400",
    "old-key": null
  },
  "more": false,
  "players": [
    { "id": "k2v9x0qa", "name": "guest-k2v9", "color": "#00ba7c" }
  ]
}
FieldDescription
revRoom revision. Pass it back as since to get only newer changes.
changesKeys changed since since. null means the key was deleted.
moretrue when more than 1,000 changes are waiting. Request again.
playersPresent only when p, n and c (id, name, color) are sent.

Try it

GETwww.embedpad.app/api/room/pixel-wall?since=0

This hits the real public room for the demo pixel wall. Send once to get the full state, then send again to get only what changed.

Reference

Apps API

POST/api/sitescreate
POST/api/sites/:id/generatex-site-token
GET/api/sites/:idapp + versions
GET/api/sites/:id/coinpaired coin
create & build
# Start a new app. Keep the token: it's the only way to edit it.
curl -X POST "https://www.embedpad.app/api/sites" \
  -H "content-type: application/json" \
  -d '{"prompt":"A shared pixel wall","model":"anthropic/claude-sonnet-5.5"}'

# → 201 { "id": "k3n7q2xw9p", "token": "…" }

# Build it (streams the HTML as plain text while it writes)
curl -N -X POST "https://www.embedpad.app/api/sites/k3n7q2xw9p/generate" \
  -H "x-site-token: $TOKEN" \
  -H "content-type: application/json" \
  -d '{"prompt":"Make the colors pastel"}'
read
curl "https://www.embedpad.app/api/sites/k3n7q2xw9p"
# → { "site": { "id", "title", "html", "version", "model", "views", … },
#     "versions": [{ "version": 3, "prompt": "…", "createdAt": "…" }] }

curl "https://www.embedpad.app/api/sites/k3n7q2xw9p/coin?fees=1"
# → { "coin": { "mint", "symbol", "name", "feeSplit", … } | null,
#     "market": { "mcapUsd", "complete", … } | null,
#     "fees": { "status", "shareholders", … } | null }
The edit token is shown once. Anyone with it can rebuild the app, so keep it private.
Reference

Coin data API

Market data for coins paired on embedpad. Other mints return 404.

GET/api/coins/:mint?kind=market|candles|trades
coin data
MINT=...pump

curl "https://www.embedpad.app/api/coins/$MINT"                     # market + 24h stats
curl "https://www.embedpad.app/api/coins/$MINT?kind=candles&tf=5m"  # 1m 5m 15m 1h 4h 1d
curl "https://www.embedpad.app/api/coins/$MINT?kind=trades"         # latest trades
Reference

Errors

Errors return JSON with a readable message: { error: string }.

StatusWhen
400Bad body, missing prompt, key outside 1–80 characters, unknown timeframe.
403Missing or wrong x-site-token.
404Unknown app, or a mint that isn’t paired here.
413Value larger than 8 KB.
429Too many writes or builds. Wait and retry.
502pump.fun or Jupiter didn’t answer.
507Room is full (5,000 keys). Delete keys to make space.
Reference

FAQ

Do I need to write code?

No. Describe the app and ask for changes in plain words. The SDK reference is here for when you want to read or edit the HTML yourself.

Does it really play inside the post?

Yes, on X's web and apps that support player cards. X frames the app at 480×480. Where player cards aren't supported, the post shows the preview image and links to the app.

Who can edit my app?

Only the browser that created it. The edit token lives in that browser. Anyone can remix a copy, which gets its own token.

Is room data private?

No. Room state is public to anyone who has the app link. Never store secrets, emails or anything personal in it.

Can I change the fee split later?

No. pump.fun locks the split after it is set, so double-check wallets and percentages before launching.

Do I have to pair a coin?

No. Apps work fine on their own. A coin is optional and can be added any time from the builder.

Ready to build?Describe an app and post it in minutes.