# Playworks AI Agent Reference

Playworks lets AI agents create browser games, edit generated files, package builds, prepare releases, and read public game and reward data.

API base: `https://api.playworks.gala.com`

## Discovery

- Human setup page: `https://playworks.gala.com/ai`
- Markdown reference: `https://playworks.gala.com/ai.md`
- Agent index: `https://playworks.gala.com/llms.txt`
- Public API base: `https://api.playworks.gala.com`

## Authentication

Create an API key from a wallet-authenticated browser session at `/me/api-keys`, then send it as a Bearer token:

```bash
curl https://api.playworks.gala.com/auth/session \
  -H "Authorization: Bearer pw_your_key_here"
```

Treat `401` as an invalid, expired, or revoked key. Return to the browser wallet session to create a replacement key.

## API Key Policy

API keys may list existing keys for the same wallet. Creating or revoking API keys requires a wallet-authenticated browser session cookie; Bearer API keys cannot create or revoke API keys.

List keys for the same wallet:

```bash
curl https://api.playworks.gala.com/me/api-keys \
  -H "Authorization: Bearer pw_your_key_here"
```

## Public Endpoints

Public endpoints do not require a Bearer token.

- `GET /games?limit=20`
- `GET /games/GAME_SLUG`
- `GET /rewards/stats`

```bash
curl "https://api.playworks.gala.com/games?limit=20"
```

```bash
curl https://api.playworks.gala.com/games/GAME_SLUG
```

```bash
curl https://api.playworks.gala.com/rewards/stats
```

## Creator REST Workflows

Documented owned creator endpoints accept `Authorization: Bearer pw_your_key_here` after you create a key from a wallet-authenticated session. The key can only act on resources owned by the key wallet.

### Projects vs Games

Use `/creator/projects` when Playworks is the authoring workspace: create an AI project, submit prompts, edit generated files, preview, validate, then call `/creator/projects/PROJECT_ID/create-version`. Each AI project has a linked `gameId`; `create-version` packages the project files, uploads the build to that linked game, and attempts publish.

Use `/creator/games` when you already have a game record or ZIP build to manage directly: list/create/update game metadata, upload a ZIP with `/creator/games/GAME_ID/versions`, then publish the returned version. Do not create both a project and a game at the start of the same AI-native flow; start with projects for generated games, or games for direct build uploads.

Every public game must have a selected cover image before publish. For AI projects, create a preview and capture media before `create-version`; for direct uploads, upload a thumbnail to the linked game before publishing a version. Publish requests for games without a cover image return `400` until media is added.

Create an AI project:

```bash
curl -X POST https://api.playworks.gala.com/creator/projects \
  -H "Authorization: Bearer pw_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name":"Leaderboard Arcade","description":"Fast arcade game with token reward hooks."}'
```

List owned projects:

```bash
curl https://api.playworks.gala.com/creator/projects \
  -H "Authorization: Bearer pw_your_key_here"
```

Upload a ZIP build for a game. Upload runs build validation and does not require a GALA payment:

```bash
curl -X POST https://api.playworks.gala.com/creator/games/GAME_ID/versions \
  -H "Authorization: Bearer pw_your_key_here" \
  -F "file=@game.zip" \
  -F "versionLabel=ai-build-1"
```

Publish a version. Payment only blocks release. If the game is uploaded and ready but payment is required, this endpoint returns HTTP `402` with `code: "RELEASE_PAYMENT_REQUIRED"` and a `payment.paymentUrl` for the creator:

```bash
curl -X POST https://api.playworks.gala.com/creator/versions/VERSION_ID/publish \
  -H "Authorization: Bearer pw_your_key_here"
```

When you receive `RELEASE_PAYMENT_REQUIRED`, do not ask the user for a private key, seed phrase, wallet signature, or browser cookie. Tell the user to open `payment.paymentUrl` in a browser. The page says what GALA amount will be paid and which game will be published. The user connects their wallet, signs the GalaChain transfer, and the page publishes the version. When it confirms, it says they may close the window and return to their AI.

## AI Project REST Workflows

Submit a prompt:

```bash
curl -X POST https://api.playworks.gala.com/creator/projects/PROJECT_ID/prompts \
  -H "Authorization: Bearer pw_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Add a leaderboard, mobile controls, and clearer win feedback."}'
```

List generated files:

```bash
curl https://api.playworks.gala.com/creator/projects/PROJECT_ID/files \
  -H "Authorization: Bearer pw_your_key_here"
```

Update a file:

```bash
curl -X PUT https://api.playworks.gala.com/creator/projects/PROJECT_ID/files/src/main.js \
  -H "Authorization: Bearer pw_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"content":"export function startGame() { console.log(\"ready\"); }"}'
```

Create a preview:

```bash
curl -X POST https://api.playworks.gala.com/creator/projects/PROJECT_ID/preview \
  -H "Authorization: Bearer pw_your_key_here"
```

Capture preview media for the linked game. This selects a gameplay screenshot as the cover image and adds captured screenshots to the gallery:

```bash
curl -X POST https://api.playworks.gala.com/creator/projects/PROJECT_ID/media/capture \
  -H "Authorization: Bearer pw_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"gameplayDelayMs":1000}'
```

Or upload a cover image directly to a game:

```bash
curl -X POST https://api.playworks.gala.com/creator/games/GAME_ID/media/upload \
  -H "Authorization: Bearer pw_your_key_here" \
  -F "slot=cover" \
  -F "file=@thumbnail.png"
```

Prepare a version:

```bash
curl -X POST https://api.playworks.gala.com/creator/projects/PROJECT_ID/prepare-version \
  -H "Authorization: Bearer pw_your_key_here"
```

Create a version from an AI project. The linked game must already have a cover image. If payment is required, the response includes the same `RELEASE_PAYMENT_REQUIRED` handoff details before release can continue:

```bash
curl -X POST https://api.playworks.gala.com/creator/projects/PROJECT_ID/create-version \
  -H "Authorization: Bearer pw_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"versionLabel":"ai-generated-v1"}'
```

## MCP Usage

Claude Code and other MCP clients can run the Playworks MCP server locally. Set:

```text
PLAYWORKS_API_URL=https://api.playworks.gala.com
PLAYWORKS_API_KEY=pw_your_key_here
```

Use `playworks_login { mode: "api_key" }` or rely on `PLAYWORKS_API_KEY` auto-auth. MCP calls use the same owned-resource constraints as REST.

MCP agents should upload a cover thumbnail with `playworks_upload_media` before publishing, then upload builds. If `playworks_publish_version` or `playworks_upload_build` with `autoPublish: true` returns a release-payment-required error, show the included payment URL to the user and wait for them to complete the browser payment page.

Before upload, agents can validate and smoke-test SDK integration locally:

```text
playworks_validate_build { gameDir: "/path/to/game" }
playworks_sdk_smoke_test { mode: "auto", gameDir: "/path/to/game", stopOnFinish: true }
playworks_run_verifier {
  manifestPath: "/path/to/game/verifier.manifest.json",
  verifierPath: "/path/to/game/verifier.js",
  finishPayloadPath: "/path/from/sdk-smoke/run-finish-1.json"
}
```

For manual or AI-assisted playtesting, use interactive mode and inspect the session afterward:

```text
playworks_sdk_smoke_test {
  mode: "interactive",
  gameDir: "/path/to/game",
  continueAfterFinish: true
}
playworks_sdk_smoke_status {}
playworks_sdk_smoke_stop {}
```

`stopOnFinish` and `continueAfterFinish` are mutually exclusive. If neither is provided,
auto mode stops on the first `run.finish`, while interactive mode keeps recording until
`playworks_sdk_smoke_stop`.

## SDK Integration Checklist

If validation reports `sdk_usage_missing`, the uploaded game is not using the Playworks SDK
lifecycle yet. Follow this section before trying to publish again.

For AI agents fixing this issue, use the Markdown checklist directly:
https://playworks.gala.com/ai.md#sdk-integration-checklist

Uploaded games must use the Playworks SDK message protocol. Custom parent messages such as
`GAME_OVER`, `TURN_LOG`, or `SET_SEED` can be useful for local testing, but they do not
publish scores. The shell records scores only when the game calls `run.finish`
through `playworks.sdk.request`.

For a plain HTML/JS game, include this wrapper:

```js
const Playworks = (() => {
  const pending = new Map();
  let nextId = 1;

  function post(method, params = {}) {
    return new Promise((resolve, reject) => {
      const id = 'pw-' + nextId++;
      const timeout = setTimeout(() => {
        pending.delete(id);
        reject(new Error('Playworks SDK request timed out: ' + method));
      }, 15000);

      pending.set(id, { resolve, reject, timeout });
      window.parent.postMessage({
        type: 'playworks.sdk.request',
        id,
        method,
        params
      }, '*');
    });
  }

  window.addEventListener('message', (event) => {
    const data = event.data;
    if (!data || data.type !== 'playworks.sdk.response') return;

    const request = pending.get(data.id);
    if (!request) return;

    pending.delete(data.id);
    clearTimeout(request.timeout);

    if (data.ok) {
      request.resolve(data.result);
    } else {
      request.reject(new Error(data.error?.message || 'Playworks SDK error'));
    }
  });

  return {
    requestSession: () => post('session.get'),
    getSettings: () => post('settings.get'),
    getOwnership: () => post('ownership.get'),
    checkOwnership: (params) => post('ownership.check', params),
    startRun: (params = {}) => post('run.start', params),
    finishRun: (params) => post('run.finish', params),
    openLeaderboard: (params = {}) => post('leaderboard.open', params),
    closeLeaderboard: () => post('leaderboard.close'),
    getDailyLeaderboard: (date) => post('leaderboard.daily', { date }),
    getMyRewards: () => post('rewards.list')
  };
})();
```

Required lifecycle:

1. Call `Playworks.requestSession()` when the game boots.
2. Call `Playworks.getSettings()` if the game uses audio, mobile, or leaderboard settings.
3. If the game has creator-configured NFT rules, call `Playworks.getOwnership()` for the full evaluated scope or `Playworks.checkOwnership({ id })` for a specific configured NFT.
4. Call `Playworks.startRun()` when a scoring attempt begins.
5. Seed deterministic gameplay with `run.seed`; do not use `Math.random()` for gameplay.
6. Record score-relevant inputs as `{ tick, input, value? }` in `inputLog.events`.
7. Call `Playworks.finishRun({ runId, nonce, inputLog, clientScore, clientDurationMs })` when the game ends.
8. Use `result.authoritativeScore` as the leaderboard score.

```js
const session = await Playworks.requestSession();
const settings = await Playworks.getSettings();
const dock = await Playworks.checkOwnership({ id: 'mirandus-dock-building' });
if (dock.configured && dock.satisfied) {
  // Enable the creator-defined ability for this configured NFT.
}
const run = await Playworks.startRun();

const inputEvents = [];
inputEvents.push({ tick: currentTick, input: 'jump' });

const result = await Playworks.finishRun({
  runId: run.runId,
  nonce: run.nonce,
  inputLog: {
    version: 1,
    events: inputEvents,
    durationTicks: currentTick
  },
  clientDurationMs: elapsedMs,
  clientScore: score
});
```

Supported SDK methods:

- `session.get` -- get player session info
- `settings.get` -- get audio/mobile/leaderboard settings
- `ownership.get` -- get the evaluated NFT ownership scope configured for this game
- `ownership.check` -- check one configured NFT by `{ id }` or `{ channel, collection, category, type, additionalKey }`
- `run.start` -- begin a game run
- `run.finish` -- submit advisory client score and input log
- `leaderboard.open` / `leaderboard.daily` -- leaderboard
- `rewards.list` -- list earned rewards

## Score Verification

`run.finish` submits a client score and an input log, but the leaderboard does not trust `clientScore` as the final score. `clientScore` is advisory audit/debug data. The leaderboard score is the finite numeric value returned by the verifier entrypoint.

If your verifier cannot derive the score from the data it receives, `authoritativeScore` may be `0` even when `/runs/finish` receives a nonzero `clientScore`.

Minimal verifier manifest:

```json
{
  "schemaVersion": 1,
  "runtime": "js",
  "entrypoint": "verifyRun"
}
```

`runtime` must be `"js"` or `"wasm"`. `entrypoint` is the exported function name, not a file path. For the manifest above, `verifier.js` must export `verifyRun`.

Minimal JavaScript verifier:

```js
export function verifyRun(inputLog) {
  const events = Array.isArray(inputLog?.events) ? inputLog.events : [];
  let score = 0;

  for (const event of events) {
    if (event?.input === 'score_delta') {
      const value = Number(event.value);
      if (Number.isFinite(value)) score += value;
    }
  }

  return Math.max(0, Math.floor(score));
}
```

The verifier receives the submitted `inputLog` object:

```json
{
  "version": 1,
  "events": [
    { "tick": 12, "input": "score_delta", "value": 10 },
    { "tick": 30, "input": "score_delta", "value": 25 }
  ],
  "durationTicks": 120
}
```

The `/runs/finish` request body also includes `runId`, `nonce`, `clientScore`, and `clientDurationMs`, but those fields are not passed to the JavaScript verifier entrypoint. Do not depend on `run.clientScore` inside the verifier; reconstruct the score from `inputLog`.

Recommended score validation loop:

1. Launch the game in the Playworks shell or local debug shell.
2. Start a run.
3. Simulate enough input to produce a nonzero score.
4. Trigger the game end flow.
5. Capture the `/runs/finish` request body or SDK `run.finish` params.
6. Confirm `clientScore > 0`.
7. Run the verifier locally against that captured payload.
8. Confirm the verifier return equals the expected score. If `/runs/finish` returns `authoritativeScore: 0` while `clientScore` is nonzero, fix the verifier or input log before publishing.

With the Playworks MCP server, use:

```text
playworks_run_verifier {
  manifestPath: "/path/to/verifier.manifest.json",
  verifierPath: "/path/to/verifier.js",
  finishPayloadPath: "/path/to/captured-runs-finish.json",
  expectedScore: 35
}
```

That tool prints the exact verifier input, the returned score, and a warning with code `authoritative_score_zero` when the game submitted a nonzero `clientScore` but the verifier returned `0`.

For deploy/cache debugging, compare the stable build URL with a cache-busted URL:

```bash
curl -I https://api.playworks.gala.com/games/GAME_SLUG/build/index.html
curl -I "https://api.playworks.gala.com/games/GAME_SLUG/build/index.html?cb=$(date +%s)"
```

Useful score-debugging checks:

- Capture SDK `run.start` and `run.finish` params with Playwright network/message logging.
- Use the local debug shell to test SDK messages before upload.
- Fetch `GET /creator/games` with your API key to inspect each version's `buildCheck` and `verifierCheck` summary.
- Fetch the stable build URL and a cache-busted build URL when a newly approved version appears stale.

## Game Build Requirements

- Put `index.html` at the ZIP root.
- Keep the ZIP under 50 MB.
- Add a cover image before publish. Use a captured gameplay screenshot or upload a PNG, JPEG, or WebP thumbnail.
- Use deterministic gameplay and a seeded PRNG instead of `Math.random()`.
- Log every score-relevant player input with tick numbers so the verifier can reconstruct the score without `clientScore`.
- Include `verifier.manifest.json` and a verifier file whose exported entrypoint returns a finite numeric score.
- Communicate with the Playworks shell through the PostMessage SDK protocol.

## Safety Limits

- Do not place private keys, seed phrases, wallet secrets, session cookies, or user identifiers in game files or prompts.
- Do not attempt to pay or sign GalaChain transactions with an API key. Use the browser payment URL returned by publish.
- Do not document or call admin endpoints with API-key Bearer auth.
- Do not mix preview tokens with publish operations.
- Keep generated games suitable for public web playback and deterministic score review.
