# podda.json v2

The staged ZIP must contain this file at its root:

```json
{
  "schemaVersion": 2,
  "name": "Shared counter",
  "description": "A private counter and member leaderboard",
  "client": { "entrypoint": "index.html" },
  "sdkVersion": "2",
  "capabilities": { "kv": ["browser"] }
}
```

Optional server modules use `"server": { "entrypoint": "server.mjs", "runtime": "cloudflare-worker-module" }`. Each capability explicitly lists its allowed surfaces, for example `"kv": ["server"]` prevents direct browser access while allowing the Worker. Available capabilities are kv, files, members, notifications, and ai. Network expectations document intended exact HTTPS origins but do not grant access.

The full optional network shape is `"network": { "expectedOrigins": [{ "origin": "https://api.example.com", "purpose": "Fetch shared recipes", "surface": "server", "serverMethods": ["GET"] }] }`. Browser expectations omit serverMethods; `both` may declare methods for its server half. At most eight origins are allowed.

Paths are relative POSIX paths. Absolute paths, traversal, backslashes, symlinks, case collisions, external forms, and service workers are rejected. A remote `<script src>`, `<link href>`, or module import is rejected unless its exact origin appears in `network.expectedOrigins` — and declaring it is not granting it, so the owner must still approve the origin with set_network_access browser_directives. Bundling the file into the app avoids both steps; see get_docs(dependencies).

For the first activation, publish with an explicit null CAS pointer:

```json
{
  "action": "publish",
  "app_id": "<create_app app_id>",
  "activate": true,
  "expected_active_version_id": null,
  "artifact": { "upload_id": "<prepare_upload upload_id>" },
  "provenance": { "generator": "your-agent-name" },
  "idempotency_key": "fresh-publish-intent"
}
```

The client entrypoint is limited to 512 KiB, a generated server module to 1 MiB, a static asset to 25 MiB, and one artifact to 128 files / 32 MiB decoded.
