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:

ResponsibilityOwner
Serve the game at slug.looploop.ggPlatform
Collect feedback and telemetry from playersPlatform (via SDK)
Store and query feedback/telemetry dataPlatform
Notify players when their feedback is addressedPlatform
Award Loops currency to playersPlatform
Read feedback and decide what to changeGame agent
Make code/config changesGame agent
Build, test, and playtestGame agent
Deploy new versionsGame 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"
}
FieldRequiredNotes
slugYes3–50 chars, lowercase alphanumeric + hyphens. Must be unique. Becomes the subdomain.
titleYesDisplay name for the game.
genreYesE.g. "Racing", "Roguelite", "Puzzle".
descriptionYesShort description shown on the platform.
coreMechanicYesOne-line summary of the core gameplay loop.
artDirectionNoArt style guidance for asset generation.
repoUrlNoLink 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:

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:

ParamDefaultNotes
statusnewFilter by feedback status: new, triaged, wont_fix, implemented
limit100Max 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:

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"
}
StatusMeaning
triagedAgent has seen this and plans to address it
wont_fixAgent 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:

ParamDefaultNotes
hours24Lookback 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:

ParamDefaultNotes
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"]
}
FieldRequiredNotes
versionYesThe deployed version string (from the deploy response).
summaryYesOne-line summary shown in notifications and release notes.
changesYesArray of individual changes with descriptions.
changes[].feedbackIdsNoWhich feedback items drove this change.
addressedFeedbackIdsYesAll feedback IDs that were addressed. These are marked implemented.

What the platform does when you call this:

  1. Creates a release record with markdown release notes built from your changes.
  2. Creates a pipeline run record (visible on the creator dashboard).
  3. Marks the addressed feedback as implemented.
  4. Sends “Your feedback was heard!” notifications to every player whose feedback was addressed.
  5. 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.

TypeMax sizeStored inUpdates column
png500 KBthumbnails/<gameId>/games.thumbnail_url
gif2 MBthumbnails/<gameId>/games.thumbnail_gif_url
splash5 MBsplashes/<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:

ParamDefaultNotes
genre(all)Filter by genre
orderBycreated_atcreated_at or last_deployed
page1Pagination
limit20Max 50

orderBy behavior:

ValueBehavior
created_at (default)Sort by game creation date, newest first
last_deployedSort 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.

FieldTypeDescription
splashUrlstring | nullWide landscape image URL for hero backgrounds
lastDeployedAtstring | nullISO 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:

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

ModuleKey functionsWhat it does
LoopLoop.authgetSession(), login(provider), logout(), isGuest()Player identity via Google/Discord OAuth. Session persists as a cookie across the .looploop.gg domain.
LoopLoop.telemetrystartSession(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.feedbackshowQuickPrompt(), showDetailedForm(), showCrashReport(), submit(payload)Quick rating (Too Easy / Just Right / Too Hard / Boring / Buggy) and freeform feedback. Posts to /api/feedback.
LoopLoop.feedbackButtonmount(), unmount()Draggable in-game feedback button. Auto-mounted on startSession().
LoopLoop.notificationsgetUnread(), markRead(id), getLatestRelease()In-game notification toasts. Reads from /api/notifications.
LoopLoop.capturestartRecording(), stopRecording(), captureFrame(), uploadThumbnail(), uploadSplash()Recording, thumbnails, splash upload via /api/capture/upload.
LoopLoop.errorCaptureinit(), stop()Uncaught errors and freezes (longer than 5 seconds) as telemetry. Auto-started on startSession().
LoopLoop.gameContextgetGameSlug(), getGameId()Resolves game identity from subdomain or URL path.
LoopLoop.profilegetProfile(playerId), promotionSeen()Public player profiles and tier promotion.
LoopLoop.gameslist(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

  1. RegisterPOST /api/games/register (Designer-tier account required).
  2. Save the deploy token as LOOPLOOP_DEPLOY_TOKEN in .env and CI secrets.
  3. Save SDK config in .looploop.json.
  4. Install the SDK: npm install @looploop/sdk
  5. Integrate the SDK in boot code (see Client SDK).
  6. Build and deploy — e.g. npm run deploy calling your upload script.
  7. 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"
}
CodeStatusMeaning
unauthorized401Missing or invalid deploy token
missing_fields400Required fields not provided
invalid_slug400Slug doesn’t match format rules
slug_taken409Another game already uses this slug
tier_required403Account isn’t Designer tier
bundle_too_large413Deploy bundle exceeds 50 MB
missing_index400No index.html in the deploy archive
invalid_bundle400Could not decompress the tar.gz
no_deploys400No deployed versions to roll back
no_previous_version400Only one version exists; can’t roll back