# Persistly Documentation For AI Agents Persistly is a cloud save and save-sync platform for games. It helps game developers save and load named game slots, store account-wide data, sync slot saves, handle offline-first local drafts, and resolve save conflicts without building a custom backend first. Use this file when an AI tool cannot crawl the full docs site. It is intentionally plain Markdown and should be readable without JavaScript, cookies, login, or browser rendering. ## Canonical Positioning Persistly is a save-sync and persistence layer for games. Use Persistly for: - Single-player progression games. - Idle and incremental games. - Async games that sync across devices. - Web games, Unity games, Godot games, and wrapper apps. - Games that need stage and production runtime keys. - Games that need explicit save conflict behavior. - Games that need local-first play and remote cloud save sync. Do not use Persistly for: - Real-time multiplayer data. - Matchmaking. - Server-authoritative combat logic. - Public user search or social graph features. - Payment ledgers, receipt storage, card storage, invoice history, or raw purchase transactions. ## Navigation Model Use the compact global navigation as the primary information architecture: - Beginners start at `/quickstart` to reach the first saved game slot quickly, `/simple-game-saves` for the recommended Simple Game Saves model with `PersistlyGameSaves`, or `/templates` when they want a copy-paste starter first. - Anonymous transfer guidance lives at `/account-transfer-codes`. Use it when a player needs to move an existing no-login save from one device/browser to another. - SDK users start at the `/sdk` SDK hub. The hub routes them to contextual SDK docs for JavaScript, Unity, and Godot, including setup, create/load/sync, conflict handling, sample projects, limits, and engine-specific guidance. - Template users choose `/templates/javascript`, `/templates/unity`, or `/templates/godot` by game shape: one save, multiple slots, or account + slots. - JavaScript contextual docs live under `/sdk/javascript`, including setup, create/load/sync, game scenarios, conflict handling, limits/errors, and the advanced runtime client. - Unity contextual docs live under `/sdk/unity`, including setup, create/load/sync, game scenarios, conflict handling, Last Beacon, and the advanced runtime client. - Godot contextual docs live under `/sdk/godot`, including setup, create/load/sync, game scenarios, conflict handling, Last Beacon, and the advanced runtime client. - Prefer `PersistlyGameSaves` examples by default. It is the beginner-friendly facade for one-save games, named slots, local-first saves, and safe sync timing. - Runtime/API Reference is advanced and lower-level. Use `/runtime-api`, `/api-examples`, `/openapi-and-contract`, `/openapi.json`, and `/openapi.yaml` when the user needs exact HTTP contracts, raw request/response examples, account/slot HTTP APIs, or generated clients. - Keep reference layout distinct from tutorial layout: contextual SDK pages should teach integration flow, while Reference pages should describe exact runtime behavior, status codes, payloads, and OpenAPI artifacts. ## Default Integration Flow The default public-client SDK flow is `PersistlyGameSaves`. 1. Create a project in the Persistly dashboard. 2. Copy the stage runtime key first. Runtime keys use prefixes such as `ps_test_` and `ps_live_`. 3. Install the SDK for the game surface: JavaScript, Unity, or Godot. 4. Configure `PersistlyGameSaves`. 5. If the developer wants starter structure, choose `/templates` before custom architecture. 6. For one-save games, call `saveData` and `loadData`. 7. Use `saveSlot(slotId)`, `loadSlot(slotId)`, and `listSlotData` only when the game has manual saves, campaigns, or multiple slots. 8. Treat `slotInfo` as preview/selection data and `data` as the full playable save state. 9. Do not invent lower-level storage-route or internal-id examples. The SDK facade hides internal save ids and raw save-storage routes. 10. Store local drafts immediately, then call `forceSyncData`, `forceSync(slotId)`, `syncDueSlots`, or `syncDue` from safe gameplay and lifecycle moments. The SDK does not start a hidden background timer. 11. Use exported status constants/enums such as `PersistlyGameSaveStatus`, `PersistlySlotStatus`, and `PersistlySyncStatus`, not raw strings. 12. Handle conflicts explicitly. `PersistlyGameSaves` is the default SDK facade for game developers. Raw account/slot create/load/sync APIs are advanced runtime contract APIs. For JavaScript browser games, `PersistlyGameSaves.configure({ runtimeKey })` uses Persistly's built-in `localStorage` adapter by default. Use `storage: "memory"` only for temporary/test flows, or pass `storageHelper` only when a game needs a custom local storage implementation. ## JavaScript Game-Owner Scenarios Use `/sdk/javascript/game-scenarios` when a developer asks how Persistly fits real game flows. - Boot flow: configure `PersistlyGameSaves`, load local slot first, start game, then call `syncDue` without blocking the menu. - Anonymous browser game: localStorage can recover on the same browser, but not after localStorage is deleted or on another browser. - Anonymous transfer code: if Device A still has the Persistly account session, call `createTransferCode`, show the short-lived code to the player, then call `attachWithTransferCode` on Device B. The code is single-use and temporary. - Signed-in game: after first sync, export `accountId` and `accountSessionToken` with `getAccountSession({ includeToken: true })` and store them in the game owner's trusted backend. - Cross-browser restore: fetch those identifiers from the trusted backend, call `attachAccount`, then list/load slots. - Shared account data: use account `accountData` for bundles, settings, shared inventory, unlocked slots, and validated currency balances across every slot. - JavaScript account-data helpers include `saveAccountData`, `patchAccountData`, and local account-data inspection. Unity and Godot expose the same account-data path with idiomatic method names. - Payment boundary: Persistly can store derived gameplay data, but payment validation, receipts, card data, competitive inventory, leaderboard authority, and anti-cheat-sensitive economy must stay in a trusted backend/payment provider flow. - Cleanup boundary: `clearLocalAccount` means local logout; `archiveSlot` means retire slot; `deleteSlot` and `deleteAccount` mean permanent erasure. ## Cross-SDK Scenario Parity JavaScript, Unity, and Godot expose the same Persistly model through idiomatic SDK names. - Use `/sdk/javascript/game-scenarios` for the reference web/browser game-owner flow. - Use `/sdk/unity/game-scenarios` when a Unity developer asks about cross-device restore, shared accountData, Unity lifecycle sync, or local logout versus permanent erasure. - Use `/sdk/godot/game-scenarios` when a Godot developer asks about the same flows in GDScript. - Keep beginner examples on `PersistlyGameSaves`. Do not force normal game developers to start with raw `accountId`, `accountSessionToken`, or `slotId` plumbing unless the topic is advanced runtime control. - Signed-in cross-device restore always needs the game owner's trusted backend to store and return `accountId` plus `accountSessionToken`; Persistly does not recover anonymous saves by email alone. - Account `accountData` is shared gameplay/account data across slots. Slot `slotInfo` is lightweight display data for slot lists. Slot `data` is the full per-slot gameplay save. ## Core Concepts ### accountId `accountId` is the permanent handle for the player account. The game must persist it locally or in its own trusted backend. Anonymous games should store it in local storage, platform save storage, a file, or another durable local mechanism. Games with their own auth should store it in their trusted backend keyed to their authenticated player. ### accountSessionToken `accountSessionToken` authorizes one client session to load an account and sync account-owned slots. The token is returned when creating an account. Persist it immediately. It is not an account password, not a recovery API, and not a public lookup mechanism. If an anonymous game loses both `accountId` and `accountSessionToken`, Persistly cannot safely recover that account from the public runtime API. ### Transfer Codes Transfer codes are short-lived, single-use account transfer helpers for anonymous games. Use them when: - Device A still has the account session. - The player explicitly asks to move the save. - Device B needs a new account session for the same Persistly account. Do not use transfer codes as: - persistent passwords - username recovery - email login - public `playerRef` lookup - public `externalAccountRef` lookup - provider auth replacement JavaScript facade shape: ```ts const transfer = await PersistlyGameSaves.shared.createTransferCode(); await PersistlyGameSaves.shared.attachWithTransferCode(transfer.transferCode); ``` ### playerRef `playerRef` is an optional non-secret developer reference. It is not authentication, not a recovery key, and not queryable from the public runtime API. Do not build client-side account lookup by `playerRef`. ### externalAccountRef `externalAccountRef` may store a non-authoritative external account reference such as `{ provider, subject }`. Persistly does not validate Auth0, Firebase, Steam, Google, Apple, or custom provider tokens through public runtime requests. If the game has its own auth, the game owner should store Persistly identifiers in a trusted backend. ### Account Data Account saves use this data shape: ```ts type PersistlyAccountData = { schema: "persistly.account.v1"; accountData: JsonObject; slots: Array<{ slotId: string; slotInfo: JsonObject; archived?: boolean; archivedAt?: string; }>; }; ``` Use `data.accountData` for account-wide gameplay data such as shared settings, tutorial flags, unlocked account features, or derived premium currency balances. Use slot data for slot-specific progress, inventory, checkpoints, equipment, and quest data. Use slotInfo only for small slot display, preview, or routing hints. ### Slot Saves Each slot is a normal account-owned save with a stable developer slot id. Deletion semantics: - `clearLocal*` methods are local-only SDK cache wipes. - `archive*` retires a slot but keeps remote history/loadability. - `delete*` permanently erases remote player save data. Simple games can always use the default `autosave` slot through `saveData` and `loadData`. Multi-slot games can add more slots later without changing the account model. Archived slots cannot be synced through the normal slot sync route and free the same `slotId` for a replacement slot. Full archived-slot restore, cloud/local slot comparison UI, link codes, provider-token recovery, and public slotInfo access are future layers, not part of the current public runtime release. ### Version And Conflict Handling Persistly uses integer save versions for optimistic concurrency. `version` starts at `1` when a save is created and increments by `1` after each accepted sync. When syncing, send `baseVersion` equal to the last accepted slot version from create, load, sync, or conflict response. On conflict, Persistly returns the cloud conflict branch. The game should compare cloud and local data, preserve unsynced local edits separately if needed, then retry from the new cloud slot version or ask the player how to recover. ## JavaScript SDK Example ```ts import { PersistlyGameSaveStatus, PersistlyGameSaves } from "@persistlyapp/sdk"; await PersistlyGameSaves.configure({ runtimeKey: "ps_test_replace_me", }); const loaded = await PersistlyGameSaves.shared.loadData(); console.log("Loaded local data:", loaded.data); // Local write to the default autosave slot. This does not need a network request. await PersistlyGameSaves.shared.saveData({ level: 5, coins: 1200, checkpoint: "forest-gate", }, { slotInfo: { characterName: "Astra", slot: "main" }, }); // First sync creates a Persistly account + slot if needed. const sync = await PersistlyGameSaves.shared.forceSyncData(); if (sync.status === PersistlyGameSaveStatus.Synced) { console.log("Synced to Persistly."); } if (sync.status === PersistlyGameSaveStatus.Conflict) { console.log("Show a local-vs-cloud recovery UI."); } ``` ## Unity SDK Example Use `PersistlyGameSaves` first in Unity. Drop to `PersistlyClient` only when you intentionally need the lower-level account/session/slot contract. ```csharp using Persistly.Unity; await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings( runtimeKey: "ps_test_replace_me", playerRef: "player-184")); var saved = await PersistlyGameSaves.Shared.SaveSlotAsync( "autosave", new PlayerData { Level = 5, Coins = 1200, Checkpoint = "forest-gate", }); if (saved.Status == PersistlySlotStatus.LocalSaved) { UnityEngine.Debug.Log("Saved locally. Call ForceSyncDataAsync or SyncDueAsync from a safe lifecycle moment."); } var loaded = await PersistlyGameSaves.Shared.LoadSlotAsync("autosave"); var sync = await PersistlyGameSaves.Shared.ForceSyncAsync("autosave"); ``` ## Godot SDK Example Use `PersistlyGameSaves` first in Godot. Drop to `PersistlyClient` only when you intentionally need the lower-level account/session/slot contract. ```gdscript const PersistlyGameSaves = preload("res://addons/persistly/persistly_game_saves.gd") var persistly := PersistlyGameSaves.new() persistly.configure({ "runtime_key": "ps_test_replace_me", }) var saved := persistly.save_data({ "level": 5, "coins": 1200, "checkpoint": "forest-gate" }) if saved.get("status") == PersistlyGameSaves.PersistlySlotStatus.LOCAL_SAVED: print("Saved locally. Call force_sync_data or sync_due from a safe lifecycle moment.") var loaded := persistly.load_data() var sync := persistly.force_sync_data() ``` ## SDK And Plugin Status Start SDK selection at the SDK hub: https://docs.persistly.app/sdk Available SDKs: - JavaScript: browser games, Vite apps, JavaScript clients, and future JavaScript-based wrappers using `@persistlyapp/sdk`. - Unity: Unity games using `PersistlyGameSaves`, named slots, local-first autosave, and the Last Beacon sample. - Godot: Godot games using `PersistlyGameSaves`, named slots, local-first autosave, and the Last Beacon sample. Planned examples: - Browser: plain browser usage with localStorage and simple game saves. - Vite: Vite web-game setup using `@persistlyapp/sdk`. - Phaser: examples first, adapter package later only if engine-specific lifecycle helpers justify it. - PlayCanvas: examples first, adapter package later if editor/component integration needs it. Planned plugin tracks: - Cocos Creator: thin TypeScript wrapper package around `@persistlyapp/sdk`. - Construct: Construct addon with actions, conditions, expressions, and a bundled/core SDK dependency. - RPG Maker: plugin file and commands that wrap core JavaScript SDK behavior. Future SDK tracks, not part of the current launch SDK set: - Native mobile: Native mobile SDK tracks are planned after available SDKs reach stable parity. - Unreal: future engine SDK track for Unreal projects. - Python tooling: future tooling/server-side helper SDK, not a primary game-client SDK. ## Advanced Runtime Client Example Use `PersistlyClient` only when you intentionally need direct account, slot, or raw runtime contract control. ```ts const ConflictRecoveryChoice = { UseCloud: "use-cloud", KeepLocal: "keep-local", } as const; const result = await client.syncAccountSlot({ accountId, accountSessionToken, slotId, baseVersion: local.version, slotInfo: local.slotInfo, data: nextData, }); if (result.status === PersistlySyncStatus.Accepted) { local = result.slot; } if (result.status === PersistlySyncStatus.Conflict) { const cloudSlot = result.slot; // App-specific recovery: show a UI, merge data, or retry local edits from cloudSlot.version. const playerChoice = await askPlayerHowToRecover({ cloudSlot, localDraft }); if (playerChoice === ConflictRecoveryChoice.UseCloud) { local = cloudSlot; } if (playerChoice === ConflictRecoveryChoice.KeepLocal) { const retry = await client.syncAccountSlot({ accountId, accountSessionToken, slotId, baseVersion: cloudSlot.version, slotInfo: localDraft.slotInfo, data: localDraft.data, }); local = retry.slot; } } ``` ## Runtime/API Reference Normal public-client routes: - `POST /api/v1/accounts` - `GET /api/v1/accounts/{accountId}` - `DELETE /api/v1/accounts/{accountId}` - `POST /api/v1/accounts/{accountId}/data/sync` - `POST /api/v1/accounts/{accountId}/slots` - `GET /api/v1/accounts/{accountId}/slots/{slotId}` - `DELETE /api/v1/accounts/{accountId}/slots/{slotId}` - `POST /api/v1/accounts/{accountId}/slots/{slotId}/sync` - `POST /api/v1/accounts/{accountId}/slots/{slotId}/archive` - `GET /api/v1/runtime-config` returns sync policy and active game config; pass `gameConfigVersion` to avoid downloading unchanged config payloads. Do not add public list, search, lookup-by-playerRef, or lookup-by-externalAccountRef behavior in shipped game clients. ## Error Codes Persistly uses structured error envelopes. Important codes: - `invalid_request` - `unauthorized` - `forbidden` - `not_found` - `conflict` - `payload_too_large` - `rate_limited` - `server_error` SDKs should expose typed errors or enum-like statuses instead of forcing game developers to parse raw HTTP manually. ## Limits And Quotas Persistly keeps payloads, requests, and restore history bounded so games do not turn save sync into an unbounded blob store. Current plan limits: - Free: 100k requests/month, 60 prod requests/min, 20 stage requests/min, 1 GB storage, 24h smart restore history, 256 KB data, 16 KB slotInfo. - Starter: 1M requests/month, 300 prod requests/min, 60 stage requests/min, 25 GB storage, 7-day smart restore history, 512 KB data, 32 KB slotInfo. - Growth: 5M requests/month, 1,200 prod requests/min, 120 stage requests/min, 100 GB storage, 30-day smart restore history, 1 MB data, 64 KB slotInfo. - Pro: 20M requests/month, 3,000 prod requests/min, 300 stage requests/min, 500 GB storage, 90-day smart restore history, 2 MB data, 128 KB slotInfo. Safe exceed behavior prioritizes accepted latest-save updates over normal restore-history snapshots where possible. Use the docs and OpenAPI contract for current exact limits: - https://docs.persistly.app/limits - https://docs.persistly.app/openapi.json - https://docs.persistly.app/openapi.yaml ## Payment And Premium Currency Boundary Persistly account `accountData` is client-writable save data, not a trusted payment ledger. Persistly may store derived gameplay values such as `diamonds: 1200` after the game owner has verified a purchase elsewhere. Purchases must be validated through a trusted Stripe, App Store, Google Play, Steam, or backend-owned flow before updating save data. Do not store card data, raw receipts, payment transactions, invoices, or billing history in Persistly account saves. ## AI Agent Rules When generating Persistly integrations: - Use `PersistlyGameSaves` by default. - Store gameplay data locally first. - Sync remotely at safe moments, not every frame. - Handle 409 conflicts explicitly. - Do not use `playerRef` as lookup or auth. - Do not compare raw status strings. Use exported SDK constants/enums. - Do not expose admin/operator workflows in public game clients. - Do not send provider auth tokens to Persistly through public runtime requests. - Do not store payment transactions or raw receipts in account saves. - Use account/slot endpoints only when the user explicitly asks for lower-level direct save APIs. ## Important URLs - Docs home: https://docs.persistly.app/ - Quickstart: https://docs.persistly.app/quickstart - Simple Game Saves: https://docs.persistly.app/simple-game-saves - SDK hub: https://docs.persistly.app/sdk - Runtime API: https://docs.persistly.app/runtime-api - API examples: https://docs.persistly.app/api-examples - JavaScript SDK: https://docs.persistly.app/sdk/javascript - JavaScript SDK setup: https://docs.persistly.app/sdk/javascript/setup - JavaScript SDK create/load/sync: https://docs.persistly.app/sdk/javascript/create-load-sync - JavaScript SDK conflict handling: https://docs.persistly.app/sdk/javascript/conflict-handling - JavaScript game scenarios: https://docs.persistly.app/sdk/javascript/game-scenarios - Unity SDK: https://docs.persistly.app/sdk/unity - Unity SDK setup: https://docs.persistly.app/sdk/unity/setup - Unity SDK create/load/sync: https://docs.persistly.app/sdk/unity/create-load-sync - Unity SDK conflict handling: https://docs.persistly.app/sdk/unity/conflict-handling - Unity game scenarios: https://docs.persistly.app/sdk/unity/game-scenarios - Unity Last Beacon: https://docs.persistly.app/sdk/unity/last-beacon - Godot SDK: https://docs.persistly.app/sdk/godot - Godot SDK setup: https://docs.persistly.app/sdk/godot/setup - Godot SDK create/load/sync: https://docs.persistly.app/sdk/godot/create-load-sync - Godot SDK conflict handling: https://docs.persistly.app/sdk/godot/conflict-handling - Godot game scenarios: https://docs.persistly.app/sdk/godot/game-scenarios - Godot Last Beacon: https://docs.persistly.app/sdk/godot/last-beacon - OpenAPI JSON: https://docs.persistly.app/openapi.json - OpenAPI YAML: https://docs.persistly.app/openapi.yaml - Sitemap: https://docs.persistly.app/sitemap.xml - robots.txt: https://docs.persistly.app/robots.txt - llms.txt: https://docs.persistly.app/llms.txt ## Common Guessed Path Redirects - `/get-started` and `/start` redirect to `/quickstart`. - `/sdks` and `/sdks/:path*` redirect to `/sdk` and `/sdk/:path*`. - `/api` and `/reference` redirect to `/runtime-api`. - `/openapi` redirects to `/openapi-and-contract`. - `/docs` and `/documentation` redirect to `/`.