# 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"
```

### Capability-gated SDK validation

Playworks SDK integration is optional for `plain_hosted` games and `paid_unlock`
games whose access is enforced by the Playworks shell before the iframe loads.
These builds still must be valid static ZIPs with root `index.html`, relative
asset paths, and External HTTPS API calls only.

Require the SDK only when selected capabilities need in-game Playworks APIs:
session, NFT ownership, entitlement status inside the game, rewards,
leaderboards, analytics, run lifecycle, or verifier scoring. Verifier artifacts
are required for leaderboard, rewards, run lifecycle, or other verifier-backed
scoring capabilities.

If a `plain_hosted` build supplies `verifier.manifest.json` plus `verifier.js`
or `verifier.wasm`, Playworks validates and persists those optional artifacts.
Any game that calls `run.finish` for verified scoring should include them even
when its stored capability classification is still `plain_hosted`.

In-game item sales are deferred. Do not create item-store, inventory-sale, or
in-game purchase flows for v1; use paid unlock only for one-time hosted game
access.

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."}'
```

For Three.js-oriented games, create the project or first prompt with the approved template ID. This routes the first-draft brief through the `threejs-game-director` guidance from `majidmanzarpour/threejs-game-skills` while preserving Playworks SDK and static ZIP requirements:

```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":"Build a 3D asteroid runner with banking turns.","templateId":"playworks-threejs-v1"}'
```

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 packaging locally. Run SDK smoke tests and
verifier checks whenever the game uses the SDK run lifecycle or supplies
verifier artifacts:

```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

For plain hosted games and paid unlocks enforced by Playworks, SDK integration is
optional. If validation reports `sdk_usage_missing`, the selected capabilities
need in-game Playworks SDK lifecycle support. 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 with SDK-backed capabilities 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')
  };
})();
```

### Configure NFT ownership

1. Open the linked game from Creator Projects and choose **NFT ownership**.
2. Enable rules, then choose whether players need every rule or any one rule.
3. Browse all NFT classes on a GalaChain channel, or choose **Owned by me** to use an NFT held by the connected wallet.
4. Select a class, set the minimum quantity, then add each buff, ability, cosmetic, or other gameplay effect granted by that NFT.
5. Use **Add requirement** for every additional NFT. Each NFT keeps its own ordered effect list.
6. Choose whether Playworks should gate play, save the rules, then use **Check connected wallet** to test them.

Inventory shows wallet holdings. The NFT browser shows token classes that can be configured,
including classes the connected wallet does not own. Manual collection, category, type, and
additional-key entry is available under **Advanced token details**.

### AI-managed NFT context

For Gary-managed projects, configure NFT ownership on the linked game's management page before
asking Gary to add NFT-dependent gameplay. Playworks supplies Gary with a sanitized snapshot of
the configured requirement IDs, labels, token classes, quantities, access behavior, and each
NFT's creator-defined gameplay effects. Player wallet addresses, inventories, owned instance IDs, and raw
GalaChain responses are never included in the AI prompt.

Gary-managed games can use `api.checkOwnership(requirementId)` for the evaluated configured result
or `api.hasOwnership(requirementId)` for a boolean. Use only requirement IDs from the linked game
context. Saving NFT settings does not automatically submit an AI request or spend an AI credit.

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. Copy the seed into `inputLog` when the verifier needs it.
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,
    seed: run.seed,
    ruleset: 'my-game-v1',
    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-compatible custom
top-level fields are preserved, so include deterministic replay metadata the
verifier needs:

```json
{
  "version": 1,
  "seed": "the-seed-returned-by-run-start",
  "ruleset": "my-game-v1",
  "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. The `run.start` response is not injected automatically either. Do
not depend on `run.clientScore`; copy required values such as `run.seed` into
`inputLog` and reconstruct the score from that submitted object.

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`.
- Copy `run.seed`, a ruleset identifier, and any other deterministic replay metadata required by the verifier into `inputLog`.
- 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.

## External HTTPS APIs

Uploaded games may call game-owned external APIs over `https://` using browser request APIs such as `fetch`, `XMLHttpRequest`, or `navigator.sendBeacon`.

Do not use insecure `http://` URLs, `WebSocket`, `ws://`, or `wss://` transports. Package images, scripts, audio, and other game assets in the ZIP instead of loading them from external CDNs.

Playworks platform features still must use the Playworks SDK message protocol: sessions, run lifecycle, rewards, ownership checks, leaderboards, and wallet/platform interactions should not be called through direct API routes.

## 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.
