Real-time multiplayer you copy, paste, play.

You don't set up a server. You don't write sockets. You don't type a project id. Spark hands you a live multiplayer room — you paste a game into it, and it works.

The one thing you do first
1
Download the game (click the button above — you get spark-game.html).
2
Double-click it. It opens straight from your desktop (file://) — no server, no setup, no terminal.
3
Press PLAY. Spark auto-creates a real hosted https:// room and puts you in it. It gives you a share link — send it to friends.
🎮 Don't build the plumbing — copy the ONE pattern

Everything below reads Spark.project.id from the URL for you, so there is nothing to invent — no project id, no socket code, no protocol. Start with the-one-pattern (the tiny canonical example) and grows into Blob Arena (a full game).

Open it, View Source → copy the whole file, then replace only the drawing/game logic. Download it and double-click — it works from file:// too.

1 The only two commands you need
javascript — put this at the top of any of your HTML gamescopy
<script src="https://spark.boqsc.eu/spark-client.js"></script>
// That's it. Now on your hosted page you have:
//   Spark.project.id  → your public sp_... (auto-read from the URL, never type it)
//   Spark.connect()   → opens the realtime socket (explains any error in plain English)
javascript — the one call that connects youcopy
const ws = await Spark.connect(Spark.project.id, 'arena');
// Spark.project.id is read from the URL automatically. You never invent a room
// or a project id. If it's wrong, Spark.connect tells you what's wrong.
⚠ The #1 mistake: opening a downloaded game from file://

When you double-click a downloaded .html file, your browser opens it as file:///C:/Users/.../game.html. A file:// page cannot talk to a server, so Spark can't connect and it fails silently. You'll see something like this in the console:

WebSocket connection to 'wss://spark.boqsc.eu/api/socket?...' failed:
Unsafe attempt to load URL file:///C:/Users/.../game.html ...
'file:' URLs are treated as unique security origins.

The fix: never serve the game from file://. Spark gives it a real https:// address in one line — add this to your game and it handles both double-clicks and joins automatically:

<script src="https://spark.boqsc.eu/spark-client.js"></script>
<script>
  Spark.deploy({ index: document.documentElement.outerHTML })
    .then(app => { location.href = app.scaffold.site_url; });
</script>

Or just click the button above and copy the page it opens — that page is already on https://.

Rule 1: never type a project id. The sp_… value is given to you by Spark — it's in the URL of your live page and it's read for you by Spark.project.id. Don't write demo_arena or anything made-up; use the real one.
2 Want your own HTML file to be the game? Do this.
javascript — put this in your HTML and it handles double-clicks + deploy for youcopy
<script src="https://spark.boqsc.eu/spark-client.js"></script>
<script>
// This makes ANY single HTML file a Spark multiplayer game.
const app = await Spark.deploy({ index: document.documentElement.outerHTML });
location.href = app.scaffold.site_url;   // go to the live https:// version
</script>
Try it right now: open blob-arena.html — a complete downloadable game. Download it, double-click it, press PLAY MULTIPLAYER, and it deploys itself and connects. That's the whole flow.
Advanced API reference (database, storage, run code, register …)
3 Database (5 MB)
bash — raw SQL, one statement per callcopy
curl -s -X POST https://spark.boqsc.eu/api/db/query \
  -H "Authorization: Bearer $KEY" \
  -d '{"sql":"CREATE TABLE scores(level INT, score INT)"}'

curl -s -X POST https://spark.boqsc.eu/api/db/query \
  -H "Authorization: Bearer $KEY" \
  -d '{"sql":"INSERT INTO scores VALUES(1, 9000)"}'

curl -s -X POST https://spark.boqsc.eu/api/db/query \
  -H "Authorization: Bearer $KEY" \
  -d '{"sql":"SELECT * FROM scores"}'
# → {"ok":true,"columns":["level","score"],"rows":[[1,9000]],"usage":{...}}
3 Storage & hosted site (100 MB)
bash — upload files, then they are livecopy
curl -s -X POST "https://spark.boqsc.eu/api/files?path=index.html" \
  -H "Authorization: Bearer $KEY" \
  --data-binary '<h1>hi</h1>'

# your site, live (no server restart needed):
curl -s https://spark.boqsc.eu/~/sp_.../index.html
# download API:  /api/download?path=...   (add &inline=1 to preview)
# list:          /api/files           delete: /api/files?path=...  (DELETE)
4 Realtime multiplayer — binary by default
html — one script tag gives you the codec + interpolation helpercopy
<script src="https://spark.boqsc.eu/spark-client.js"></script>
// provides SparkBinary (compact binary codec) and SparkInterp (smooth
// snapshot interpolation using the server ts/seq stamps).
javascript — connect, send, listen (binary is the default protocol)copy
const ws = new WebSocket(`wss://spark.boqsc.eu/api/socket?project=PROJECT_ID&room=alpha`);
ws.binaryType = 'arraybuffer';   // compact binary by default
ws.onmessage = e => { const m = e.data.byteLength !== undefined
  ? SparkBinary.decode(e.data) : JSON.parse(e.data);
  if (m.type === 'event') console.log(m.nickname, m.event, m.data); };
ws.send(SparkBinary.encode({type:'event', event:'move', data:{x:0.5, y:0.5}}));
// ~60% smaller frames than JSON, per-room ts/seq for interpolation, and
// server-authoritative push: POST /api/broadcast {room, event, data}.
// Legacy JSON clients can opt in with ?fmt=j.
Best practices for smooth multiplayer
javascript — dead reckoning + interpolation (the recommended setup for remote players)copy
const interp = new SparkInterp({ delayMs: 30, historyMs: 400, extrapolateMs: 500 });
// delayMs: render slightly behind the newest sample (smoothing)
// extrapolateMs: dead reckoning — glide through delivery gaps so remote
//   players never freeze-then-teleport; the render clock is derived from the
//   server ts, so observer-side movement can't shift it.
// Full playbook: https://guide.boqsc.eu/multiplayer-performance.html
Checklist for zero-problem multiplayer.
  • Use the binary protocol (default) — no ?fmt=j.
  • Event relay for positions in small/medium rooms (lowest latency); ?snap=<hz> only for very large rooms where bandwidth matters more.
  • Dead reckoning: SparkInterp({ extrapolateMs }) for remote players.
  • Send positions ~every 25 ms, stay under the 480/10 s room budget.
  • Uploads are no-cache now — they go live immediately; still version script tags out of habit.
  • Measure in-game (arrival timestamps), never by polling the browser — polling induces the gaps you are trying to measure.
Reference: NEON ARENA · full guide: guide.boqsc.eu/multiplayer-performance.html
5 Run code
bash — sandboxed Pythoncopy
curl -s -X POST https://spark.boqsc.eu/api/run \
  -H "Authorization: Bearer $KEY" \
  -d '{"code":"print(6*7)"}'
# → {"ok":true,"exit_code":0,"stdout":"42\n","stderr":""}
6 Register it later
bash — claim a username + password to manage the keycopy
curl -s -X POST https://spark.boqsc.eu/api/register \
  -H "Authorization: Bearer $KEY" \
  -d '{"username":"sam","password":"hunter2"}'
# forgot the key? login, then rotate for a fresh one:
# POST /api/login {username,password} → info   |   POST /api/rotate {username,password} → new key
Everything included
⚡

Zero-setup start

One click scaffolds a live multiplayer page and mints a key. No signup, no setup.

🗄

5 MB database

Per-key SQLite. Run raw SQL via /api/db/query. Enforced quota, one statement per call.

📦

100 MB storage

Upload, download, list and delete files with the key. Every file has a public URL.

🌐

Hosted site

Drop in index.html and your page is live at spark.boqsc.eu/~/<id>/.

🎮

Realtime multiplayer

WebSocket rooms on a compact binary protocol by default (with ts/seq stamps and a client helper). Presence, events, direct messages, server push via /api/broadcast.

⚙

Run code

Sandboxed Python with a timeout. No OS, network or file access.

🔑

Key only

Issued instantly, no account needed. Register a username + password later to manage and rotate it.

How it works. Every key is a project. The id (like sp_...) is public — use it to address your site and realtime rooms. The key (like spk_...) is secret — send it as Authorization: Bearer <key> on every API call. Keys are stored hashed server-side; the raw key is shown once. Quotas are enforced per project, and all API responses are CORS-open for any origin.