API Reference
This reference describes how games hosted in their own repositories interact with the LoopLoop platform: registration, deployment, the feedback loop, the browser SDK, capture uploads, public game listing, and the full agent workflow for autonomous game evolution.
How It Works
LoopLoop evolves web games from player feedback. Games are served at <slug>.looploop.gg. The platform handles player identity, feedback collection, telemetry, notifications, and the Loops currency.
With the external game API, each game lives in its own repository with its own AI agent. The platform becomes a service layer:
| Responsibility | Owner |
|---|---|
Serve the game at slug.looploop.gg | Platform |
| Collect feedback and telemetry from players | Platform (via SDK) |
| Store and query feedback/telemetry data | Platform |
| Notify players when their feedback is addressed | Platform |
| Award Loops currency to players | Platform |
| Read feedback and decide what to change | Game agent |
| Make code/config changes | Game agent |
| Build, test, and playtest | Game agent |
| Deploy new versions | Game agent |
1. Game Registration
Before deploying, register your game to get a deploy token.
POST /api/games/register
Auth: Player JWT cookie (must be Designer tier).
Request body:
{
"slug": "zombiehill",
"title": "ZombieHill",
"genre": "Racing",
"description": "Crush zombies on chaotic off-road hills",
"coreMechanic": "Physics-based hill climbing with zombie obstacles",
"artDirection": "Dark cartoon with neon accents",
"repoUrl": "https://github.com/you/zombiehill"
}
| Field | Required | Notes |
|---|---|---|
slug | Yes | 3–50 chars, lowercase alphanumeric + hyphens. Must be unique. Becomes the subdomain. |
title | Yes | Display name for the game. |
genre | Yes | E.g. "Racing", "Roguelite", "Puzzle". |
description | Yes | Short description shown on the platform. |
coreMechanic | Yes | One-line summary of the core gameplay loop. |
artDirection | No | Art style guidance for asset generation. |
repoUrl | No | Link to the source repository. |
Response (201):
{
"gameId": "a1b2c3d4-...",
"slug": "zombiehill",
"deployToken": "ll_8f3a2b1c4d5e6f7a8b9c0d1e2f3a4b5c",
"status": "registered",
"sdkConfig": {
"gameId": "a1b2c3d4-...",
"gameSlug": "zombiehill",
"apiBase": "https://zombiehill.looploop.gg/api"
}
}
Save the deployToken — it authenticates all subsequent API calls. Store it as a secret in your CI/CD system (e.g. GitHub Actions secret LOOPLOOP_DEPLOY_TOKEN).
Save the sdkConfig values in your .looploop.json:
{
"slug": "zombiehill",
"gameId": "a1b2c3d4-...",
"apiBase": "https://zombiehill.looploop.gg/api"
}
2. Authentication
All API calls except registration use the deploy token as a Bearer token:
Authorization: Bearer ll_8f3a2b1c4d5e6f7a8b9c0d1e2f3a4b5c
The token is scoped to a single game. It grants access to deploy, read feedback, read telemetry, and report changes — but only for that game.
3. Deploy API
POST /api/deploy — Upload a new version
Build your game to a dist/ directory, tar + gzip it, and upload.
Auth: Bearer deploy token.
Request: Either multipart/form-data with a bundle field, or a raw application/gzip body.
Constraints:
- Max bundle size: 50 MB
- Must contain an
index.htmlat the root - If all files share a common directory prefix (e.g.
dist/), it is stripped automatically
Example:
# Build
npm run build
# Package
tar -czf bundle.tar.gz -C dist .
# Upload
curl -X POST https://zombiehill.looploop.gg/api/deploy \
-H "Authorization: Bearer $LOOPLOOP_DEPLOY_TOKEN" \
-H "Content-Type: application/gzip" \
--data-binary @bundle.tar.gz
Response (201):
{
"deployId": "d1e2f3a4-...",
"version": "v1709942400000",
"slug": "zombiehill",
"fileCount": 47,
"totalBytes": 2340567,
"deployUrl": "https://zombiehill.looploop.gg"
}
Each deploy is immutable. Files are stored in object storage at games/<slug>/<version>/, and the game’s current_deploy_version pointer is updated. The game goes live immediately.
On the first successful deploy, the game status changes from registered to live.
POST /api/deploy/rollback — Revert to previous version
Auth: Bearer deploy token.
Request body: None.
Response (200):
{
"rolledBackTo": "v1709856000000",
"previousVersion": "v1709942400000",
"slug": "zombiehill",
"deployUrl": "https://zombiehill.looploop.gg"
}
Rollback swaps the version pointer back one step. The previous version’s files remain in storage, so rollback is instant.
4. Feedback Digest API
These endpoints let your game’s agent read player feedback and telemetry to make data-driven decisions.
GET /api/pipeline/feedback-digest — Get pending feedback
Auth: Bearer deploy token.
Query parameters:
| Param | Default | Notes |
|---|---|---|
status | new | Filter by feedback status: new, triaged, wont_fix, implemented |
limit | 100 | Max rows to return (capped at 500) |
Response (200):
{
"gameId": "a1b2c3d4-...",
"slug": "zombiehill",
"feedback": [
{
"id": "fb-001",
"player_id": "p-123",
"session_id": "sess-456",
"game_version": "v1709942400000",
"quick_rating": "too_hard",
"category": null,
"text": null,
"session_metrics": "{\"durationSeconds\":45,\"enemiesKilled\":12}",
"status": "new",
"created_at": "2026-03-08T14:30:00.000Z"
},
{
"id": "fb-002",
"player_id": "p-789",
"session_id": "sess-012",
"game_version": "v1709942400000",
"quick_rating": null,
"category": "balance",
"text": "The third hill is impossible, zombies spawn too fast",
"session_metrics": null,
"status": "new",
"created_at": "2026-03-08T15:12:00.000Z"
}
],
"quickRatingDistribution": {
"too_hard": 12,
"just_right": 8,
"too_easy": 3,
"buggy": 2,
"boring": 1
},
"pendingCounts": {
"new": 26,
"triaged": 5
},
"limit": 100,
"statusFilter": "new"
}
Feedback types:
- Quick rating (
quick_rating): One oftoo_easy,just_right,too_hard,boring,buggy. Shown to players after each session. - Detailed feedback (
category+text): Freeform text with a category. Categories:balance,content,bug,art,ux,other. - Session metrics (
session_metrics): JSON blob with duration, kills, level, cause of death — attached to the session that generated the feedback.
POST /api/pipeline/triage — Mark feedback as processed
After your agent has read and analyzed feedback, mark it so it doesn’t appear in the next digest.
Auth: Bearer deploy token.
Request body:
{
"feedbackIds": ["fb-001", "fb-002", "fb-003"],
"status": "triaged"
}
| Status | Meaning |
|---|---|
triaged | Agent has seen this and plans to address it |
wont_fix | Agent has decided not to act on this (out of scope, duplicate, etc.) |
Constraints: Max 200 IDs per request.
Response (200):
{
"updated": 3,
"status": "triaged"
}
GET /api/pipeline/telemetry-summary — Get session analytics
Auth: Bearer deploy token.
Query parameters:
| Param | Default | Notes |
|---|---|---|
hours | 24 | Lookback period (max 168 = 7 days) |
Response (200):
{
"gameId": "a1b2c3d4-...",
"periodHours": 24,
"totalSessions": 142,
"avgDurationSeconds": 67.3,
"avgEnemiesKilled": 23.1,
"avgLevelReached": 4.2,
"topDeathCauses": [
{ "cause": "zombie_horde", "count": 45 },
{ "cause": "fall_damage", "count": 32 },
{ "cause": "fuel_empty", "count": 28 }
],
"errorStats": {
"errorEvents": 3,
"freezeEvents": 1,
"sessionsWithErrors": 2,
"errorSessionRate": 0.014,
"orphanSessions": 5,
"orphanSessionRate": 0.035
}
}
GET /api/pipeline/baselines — Get metric baselines
Retrieve stored playtest baselines so your agent can compare new playtest results against them.
Auth: Bearer deploy token.
Query parameters:
| Param | Default | Notes |
|---|---|---|
version | (latest) | Filter baselines by a specific game version |
Response (200):
{
"gameId": "a1b2c3d4-...",
"versionFilter": "latest",
"baselines": {
"survival": [
{ "metric": "avg_duration", "value": 65.2, "version": "v1709942400000", "updatedAt": "2026-03-08T06:00:00Z" },
{ "metric": "avg_kills", "value": 22.0, "version": "v1709942400000", "updatedAt": "2026-03-08T06:00:00Z" }
],
"aggressive": [
{ "metric": "avg_duration", "value": 45.1, "version": "v1709942400000", "updatedAt": "2026-03-08T06:00:00Z" },
{ "metric": "avg_kills", "value": 38.5, "version": "v1709942400000", "updatedAt": "2026-03-08T06:00:00Z" }
]
}
}
5. Report Changes API
After deploying a new version, tell the platform what changed and which feedback was addressed. This triggers player notifications and Loops awards.
POST /api/pipeline/report-changes
Auth: Bearer deploy token.
Request body:
{
"version": "v1709942400000",
"summary": "Reduced zombie spawn rate on hill 3 based on player feedback",
"changes": [
{
"description": "Lowered zombie_spawn_rate from 2.5 to 1.8 on hill difficulty 3",
"feedbackIds": ["fb-002"]
},
{
"description": "Added fuel pickup on hill 3 midpoint",
"feedbackIds": ["fb-001", "fb-004"]
}
],
"addressedFeedbackIds": ["fb-001", "fb-002", "fb-004"]
}
| Field | Required | Notes |
|---|---|---|
version | Yes | The deployed version string (from the deploy response). |
summary | Yes | One-line summary shown in notifications and release notes. |
changes | Yes | Array of individual changes with descriptions. |
changes[].feedbackIds | No | Which feedback items drove this change. |
addressedFeedbackIds | Yes | All feedback IDs that were addressed. These are marked implemented. |
What the platform does when you call this:
- Creates a release record with markdown release notes built from your changes.
- Creates a pipeline run record (visible on the creator dashboard).
- Marks the addressed feedback as
implemented. - Sends “Your feedback was heard!” notifications to every player whose feedback was addressed.
- Awards Loops to those players (5 for Players, 10 for Curators, 15 for Designers).
Response (201):
{
"releaseId": "r-001",
"pipelineRunId": "pr-001",
"version": "v1709942400000",
"feedbackAddressed": 3,
"notificationsCreated": true
}
6. Capture & Image Upload API
POST /api/capture/upload
Upload thumbnails, animated previews, or splash images for your game.
Auth: Player JWT cookie.
Request: multipart/form-data with fields: file, gameId, type.
| Type | Max size | Stored in | Updates column |
|---|---|---|---|
png | 500 KB | thumbnails/<gameId>/ | games.thumbnail_url |
gif | 2 MB | thumbnails/<gameId>/ | games.thumbnail_gif_url |
splash | 5 MB | splashes/<gameId>/ | games.splash_url |
Response (201):
{ "url": "/cdn/thumbnails/a1b2c3d4/1709942400000.png", "id": "..." }
The splash type is used for the wide landscape hero background on the LoopLoop marketing site. Games with a splash image and recent deploys can be featured in the hero section.
Example (curl):
curl -X POST https://zombiehill.looploop.gg/api/capture/upload \
-H "Cookie: session=..." \
-F "file=@splash.png" \
-F "gameId=a1b2c3d4-..." \
-F "type=splash"
Example (SDK):
const blob = await LoopLoop.capture.captureFrame({ width: 1280 });
await LoopLoop.capture.uploadSplash(gameId, blob);
7. Games List API
GET /api/games
Query parameters:
| Param | Default | Notes |
|---|---|---|
genre | (all) | Filter by genre |
orderBy | created_at | created_at or last_deployed |
page | 1 | Pagination |
limit | 20 | Max 50 |
orderBy behavior:
| Value | Behavior |
|---|---|
created_at (default) | Sort by game creation date, newest first |
last_deployed | Sort by most recent successful deploy, newest first (games never deployed sort last) |
The response includes for each game: id, title, slug, genre, description, status, creatorName, creatorTier, thumbnailUrl, thumbnailGifUrl, splashUrl, lastDeployedAt, isFavorited, createdAt.
| Field | Type | Description |
|---|---|---|
splashUrl | string | null | Wide landscape image URL for hero backgrounds |
lastDeployedAt | string | null | ISO timestamp of the most recent successful deploy |
GET /api/games/:id
Returns full game detail including all the above plus coreMechanic, artDirection, deployUrl, generatedAt, and a creator object.
Hero feature (how listing ties to the homepage)
The marketing site can use GET /api/games?orderBy=last_deployed&limit=1 to drive a hero section:
- Background: the game’s
splashUrl(falls back to a blurredthumbnailUrlif no splash is set) - Game frame:
thumbnailGifUrl(falls back tothumbnailUrl) - Title, genre, status: from registration data
- “Updated Xh ago”: derived from the most recent successful pipeline deploy
8. Client SDK
The SDK runs in the player’s browser and handles player-facing platform features.
npm install @looploop/sdk
Direct to gameplay
Games must boot straight into gameplay: no main menu or title screen as the first interactive screen. The first thing the player sees is the game running. A login overlay is fine if it does not block gameplay.
The scene chain is Boot/Loading → Gameplay. The menu is only reachable after a run ends (e.g. from Game Over), never before.
Initialization
import { LoopLoop } from '@looploop/sdk';
// The SDK auto-detects the game slug from the subdomain (zombiehill.looploop.gg)
const session = await LoopLoop.auth.getSession();
const sessionId = crypto.randomUUID();
LoopLoop.telemetry.startSession(sessionId, '0.1.0');
startGame();
SDK modules
| Module | Key functions | What it does |
|---|---|---|
LoopLoop.auth | getSession(), login(provider), logout(), isGuest() | Player identity via Google/Discord OAuth. Session persists as a cookie across the .looploop.gg domain. |
LoopLoop.telemetry | startSession(id, version), emit(event), endSession(data), flush() | Batches gameplay events and sends them to /api/telemetry. Auto-flushes every 10s and on page unload via sendBeacon. |
LoopLoop.feedback | showQuickPrompt(), showDetailedForm(), showCrashReport(), submit(payload) | Quick rating (Too Easy / Just Right / Too Hard / Boring / Buggy) and freeform feedback. Posts to /api/feedback. |
LoopLoop.feedbackButton | mount(), unmount() | Draggable in-game feedback button. Auto-mounted on startSession(). |
LoopLoop.notifications | getUnread(), markRead(id), getLatestRelease() | In-game notification toasts. Reads from /api/notifications. |
LoopLoop.capture | startRecording(), stopRecording(), captureFrame(), uploadThumbnail(), uploadSplash() | Recording, thumbnails, splash upload via /api/capture/upload. |
LoopLoop.errorCapture | init(), stop() | Uncaught errors and freezes (longer than 5 seconds) as telemetry. Auto-started on startSession(). |
LoopLoop.gameContext | getGameSlug(), getGameId() | Resolves game identity from subdomain or URL path. |
LoopLoop.profile | getProfile(playerId), promotionSeen() | Public player profiles and tier promotion. |
LoopLoop.games | list(params), create(brief), toggleFavorite(gameId) | Browse games, create games, favorites. |
Game state contract
Expose a window.__gameState object for playtest bots and platform tooling:
window.__gameState = {
status: 'playing',
sessionId: sessionId,
elapsedSeconds: 42.5,
gameVersion: '0.1.0',
player: {
x: 320, y: 480,
health: 80, maxHealth: 100,
level: 3, xp: 450, xpToNextLevel: 600,
alive: true,
},
entities: {
enemies: [{ id: 'e1', type: 'zombie', x: 100, y: 200, health: 30 }],
pickups: [{ id: 'p1', type: 'fuel', x: 400, y: 300 }],
},
metrics: {
enemiesKilled: 23,
damageDealt: 1450,
damageTaken: 320,
itemsCollected: 5,
levelsGained: 2,
},
};
status may be loading | playing | paused | game_over | transition | menu.
9. Agent Workflow
Recommended flow for an agent evolving a game autonomously:
1. Fetch feedback GET /api/pipeline/feedback-digest?status=new
2. Fetch telemetry GET /api/pipeline/telemetry-summary?hours=24
3. Fetch baselines GET /api/pipeline/baselines
|
v
4. Analyze & decide (agent logic — triage, prioritize, plan changes)
|
v
5. Mark as triaged POST /api/pipeline/triage
|
v
6. Make code changes (edit source, config, assets in the game repo)
|
v
7. Build & playtest npm run build && run local playtest bots
|
v
8. Deploy POST /api/deploy (tar.gz of dist/)
|
v
9. Report changes POST /api/pipeline/report-changes
(triggers notifications + Loops awards)
The agent can run this loop on any schedule — daily, on every push, or when new feedback accumulates.
10. Game repo structure
my-game/
.looploop.json # slug, gameId, apiBase
.env # LOOPLOOP_DEPLOY_TOKEN (gitignored)
.github/workflows/
deploy.yml # Auto-deploy on push to main
scripts/
deploy.mjs # Build + tar + upload script
index.html
src/
config/
guardrails/ # Agent boundaries, tunable bounds, design notes
gdd/
public/
package.json
tsconfig.json
vite.config.ts
AGENTS.md # Instructions for the game’s AI agent
.looploop.json
{
"slug": "zombiehill",
"gameId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"apiBase": "https://zombiehill.looploop.gg/api"
}
GitHub Actions example (.github/workflows/deploy.yml)
name: Deploy to LoopLoop
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- run: node scripts/deploy.mjs
env:
LOOPLOOP_DEPLOY_TOKEN: ${{ secrets.LOOPLOOP_DEPLOY_TOKEN }}
11. Getting started
- Register —
POST /api/games/register(Designer-tier account required). - Save the deploy token as
LOOPLOOP_DEPLOY_TOKENin.envand CI secrets. - Save SDK config in
.looploop.json. - Install the SDK:
npm install @looploop/sdk - Integrate the SDK in boot code (see Client SDK).
- Build and deploy — e.g.
npm run deploycalling your upload script. - Visit
https://<slug>.looploop.gg— the game is live.
If you are migrating a game from the LoopLoop monorepo, the platform repository includes a migration script that copies source, config, guardrails, and assets into a standalone layout:
npx tsx scripts/migrate-game-to-repo.ts --game=zombiehill --out=../zombiehill-standalone
Run it from a checkout of the LoopLoop repo; adjust --out to your target directory.
12. Error responses
All endpoints return JSON errors in a consistent format:
{
"error": "error_code",
"message": "Human-readable description"
}
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid deploy token |
missing_fields | 400 | Required fields not provided |
invalid_slug | 400 | Slug doesn’t match format rules |
slug_taken | 409 | Another game already uses this slug |
tier_required | 403 | Account isn’t Designer tier |
bundle_too_large | 413 | Deploy bundle exceeds 50 MB |
missing_index | 400 | No index.html in the deploy archive |
invalid_bundle | 400 | Could not decompress the tar.gz |
no_deploys | 400 | No deployed versions to roll back |
no_previous_version | 400 | Only one version exists; can’t roll back |