See exactly what your testers did. Watch the moment they stalled, the level they skipped, the build that broke pacing. Playloop turns your existing event stream into a plain-English playthrough report, no video, no manual upload, no scrubbing through recordings.
Already know what you came for? Jump straight in.
Plain-English narratives per session, per tester, per build. Skim the headlines in 30 seconds, drill into the drop patterns when something looks off.
Drop the SDK in, hit Play, and sessions show up in the dashboard within seconds. Replay the event timeline, see who is online right now, scrub through what happened.
Multi-field feedback forms that fire on milestones, deaths, or quit. Responses land beside the session that triggered them, not in a separate inbox.
Invite collaborators with the right role (Admin, Member, Viewer). Testers stay separate. Owner pays once, the whole team sees what shipped.
They open the game and play. Nothing about their experience changes.
The SDK quietly captures the events your game already fires, milestones, deaths, settings changes, anything you Track().
Replay the timeline, scan the milestones, see exactly where the session stalled and what came right before.
A plain-English report on what worked, what didn’t, and where players got stuck. Skim the headlines in 30 seconds.
Testers tend to tell you a level was “a bit confusing.” Playloop tells you which step it was, which UI element they walked past, which language cohort it happened to, and whether your last patch fixed it. Four examples of the shape you’ll see:
“67% of players never picked up the second key.”
Tester journal said the level was “a bit confusing.” The summary shows the actual stall: the door light was the same color as the floor lights. Two-thirds of players walked past it.
“Japanese players opened settings 12× more often than English.”
Not a bug, they were hunting for a key remap. The English binding made sense; the JP one didn’t. Easy to fix once Playloop flagged the divergence.
“Act 2 reach dropped from 92% to 64% after v0.4.8.”
You bumped a single damage value. Playloop tells you the patch broke pacing the moment the new sessions come in, you don’t wait for a Discord complaint.
Six cards. Top-to-bottom is the path from sign-up to comparing builds. The last one is optional.
Playloop watches the event stream your game already emits and tells you where players stall, what they skip, and which builds got harder, in plain English.
Drop in the SDK for your engine. One file for Unity or Godot, one package for Python or TypeScript. No video processing, no manual upload.
The fastest path is the SDK for your engine. Every SDK speaks the same /api/telemetry contract under the hood, pick the one that matches your runtime and the rest is identical across engines. Want to see what the result looks like before you wire anything up? Browse the live demo (real game, real cohort, no signup).
Skip the code for a second. Here’s the whole flow in plain English:
Install the SDK, fire a session, see what your players did. That’s the product. Everything below this point is optional reference for when you want to tune the analysis, plug Playloop into the rest of your stack, or build on top of it. Skim when you need it; skip when you don’t.
The outcome cards above are the fastest path to value. Everything below is the “want to know more” layer: how the pipeline is shaped, the exact HTTP contracts the SDKs speak, how to tune the AI, and the surfaces that back the dashboard. Skip around, the deep-dive pages are linked inline.
Reshape the AI output, how it talks, what it prioritizes, how prescriptive it gets.
Tone (plain vs analyst), max findings, strictness, drop-pattern thresholds, segment-split floors.
A short prompt about YOUR game so summaries read like a human analyst who knows your design.
Pick OpenAI / Anthropic / Gemini, paste your own key, set a monthly spend cap.
Players right now, 24-hour peak, all-time peak, and a 5-minute concurrency chart. How "online" is defined, refresh cadence, what is not counted.
A session is one playthrough (open the game, play, close it). A tester is a person; a build is a version of your game; an event is one thing the player did. Everything you see in the dashboard is one of those things, viewed across an environment (demo, production, staging, your call).
{ name, timestamp, data }, emitted from your game via track(). High-frequency events (click, heartbeat, hold) are configured as noise per-game so AI doesn't drown in them.
From process start to quit. Holds the full event stream as a transcript + per-event counts. AI extracts structured insights once a session ends.
Grouped by metadata.gameVersion, every session tagged with a version rolls up into that build. Get play time, engagement rate, top friction events, and an AI build-level narrative.
The SDK is the primary path, but it's not the only one. You can record gameplay manually, pipe Discord transcripts in, or hit the REST endpoint from any runtime that can speak HTTP.
The primary path. Unity / Unreal / Godot / Python / TypeScript SDKs auto-batch events every 5s, send focus + heartbeat events, gracefully shut down on quit. Best for live playtesting + future production telemetry. See the SDKs page for install + first-event snippets.
The /sessions/new page accepts .mp4 / .mov / .mp3 / .wav and transcript formats. Audio + video files run through OpenAI Whisper on the server, then the AI analyzer reads the transcript. During the beta, transcription uses your OpenAI BYO key (Anthropic/Gemini keys don't enable transcription, Whisper is OpenAI-only). Best for post-hoc reviews where you have a screen recording but no SDK in the game.
Point Discord at your per-account webhook URL (authenticated via Authorization: Bearer <management-key>) and posts from your playtest channels become sessions automatically. Per-thread segmentation, channel/thread metadata stamped on the session. This is a write endpoint, it creates sessions and kicks the analyzer, and it's the one management-key write surface. Setup steps live in Integrations → Discord; full schema in the Management API reference.
Once events start flowing in, the dashboard surfaces them at four natural scopes, session, tester, build, game, with consistent stats and AI narrative at every level.
Sessions roll up by metadata.gameVersion. See total / avg / median play time per build, engagement rate (active / wall), session count, unique testers, top friction events. Compare 0.4.9 vs 0.5.0 to see whether the latest changes helped.
One row per device_id. See their lifetime sessions, total play time, last build, geo, and an AI summary of their playstyle once you've got a few sessions to read.
GPT-mini extracts structured insights per session (stuck-points, praise, bugs, pacing, session-summary). Higher levels never re-read raw events, tester summaries consume the per-session insights; build summaries consume the per-tester summaries. Token cost stays bounded no matter how popular a build gets.
Every AI summary in Playloop, per-session, per-build, per-tester, per-game, runs through the same async pipeline. Kick a re-analyze, watch live progress in the same surface, and if nothing has changed since last time you get the cached result back instantly with no model cost.
Session, build, tester, and game summaries all use the same server-sent event feed. Hit Regenerate and you see live status (queued, running, done) without refreshing. The previous "wait and reload" experience for session re-analyze is gone.
The cache keys on the events that landed in the session plus your current tuning. If nothing has changed since the last successful run, Playloop returns the previous result without spending a token. The status badge says "Cached" so you know what happened, and there's a Force re-analyze button on every summary surface for the cases where you want to bypass it.
Per-game analysis tuning (tone, verbosity, max findings) applies to session, build, tester, and game summaries alike. Edit the tuning in the game's Settings → AI panel and the next analyze run picks it up; the cache invalidates automatically so you don't have to Force every surface.
The build-vs-build compare page now opens with an AI-written narrative. Headline plus one paragraph: what improved, what got worse, what to look at next. It sits above the friction-rate diff so the prose context is the first thing you read, with the underlying numbers right below for verification.
How to get there: open any game, pick Builds in the tab bar, pick two builds and hit Compare. The URL shape is /games/[slug]/builds/compare?a=<v>&b=<v> if you want to bookmark a specific pair.
Regenerate flow: like every other AI summary, the narrative streams in via the same server-sent event feed and caches. If nothing has changed since the last comparison you get the cached version back instantly. Hit Regenerate to pull a fresh take, or Force re-analyze to bypass the cache.
Every per-game route, Overview, Dashboard, Sessions, Users, Builds, Settings, shares one shell. The breadcrumb, game name, and lifetime stats stay put as you switch tabs. Each tab leads with an outcome-first heading so you know what the surface is for before reading anything else.
Scope selectors: the environment selector and a new build-version selector live on the tab bar itself, not on each tab’s content. Pick a build once and the Dashboard, Sessions, and Users tabs all filter to that build as you switch between them. Clear it (pick “All builds”) to see everything. Both selectors are URL-driven so deep-links survive a reload.
/settings lands you on the Account tab. Each tab owns its own route so a deep-link from an audit-log email or a doc page lands exactly where you expect, and the browser back button does what it should.
Your profile, password and 2FA, the device list and sign-out-everywhere control, the audit log of every sensitive change, and your two API keys (Ingest + Management). The danger zone (account close) lives here too.
Pick OpenAI / Anthropic / Gemini, paste a key, choose a model. Set your per-game context prompts. New on this tab: a per-user default bulk cost ceiling that caps how much one bulk re-analyze run can spend. Clamped server-side to a $0.50 floor and a $25 ceiling so the field can't be bypassed from the client.
Choose which emails you get: analysis-finished, integration errors, weekly digest, tester invites. Default ON. A 13-minute bundle window collapses bursts into one email so a wave of session analyses doesn't flood your inbox.
Select sessions on the Sessions page (or the per-game Sessions tab) and the bulk action bar appears at the bottom. The primary action is Re-analyze; a More dropdown next to it adds Export NDJSON and Delete sessions.
One JSON object per line: the full session, its events, and any insights attached. Same shape as the single-session export, but streamed, the download starts before the server has finished reading the last row, and you never hold N session bodies in memory. NDJSON because it’s jq-friendly: one line, one record. Sessions you don’t own are silently dropped from the output.
Type the literal word delete in the confirmation field, then click. By default this is a recoverable delete: the sessions disappear from your dashboard right away but are kept briefly so support can restore them if you ask, and your storage isn’t freed yet. Tick Permanently deleteto remove the sessions and their assets for good (insights and assets cascade away with their parent sessions) and reclaim the storage. Permanent delete is irreversible, so it asks you to re-verify your identity first. If you change your mind before confirming, just close the dialog, nothing has happened yet. Cross-account ids are filtered out server-side so a forwarded URL can’t reach into someone else’s data.
The dashboard drives these through internal session-cookie routes. For programmatic access from a backend, use the Management API.
Already have months of data sitting in PlayFab, GameAnalytics, Unity Analytics, Unity Gaming Services, Amplitude, or Mixpanel? Drop the export in and Playloop reads the real file each tool emits, splits it into sessions, and analyzes them. No SDK install required to backfill history.
Heads up: an analytics export holds many sessions, so it goes through Bulk import, not Add session (which is for a single playtest). Drop an export on the single-session form by mistake and Playloop recognizes it and points you here.
Playloop reads PlayFab, GameAnalytics, Unity Analytics, Unity Gaming Services (UGS), Amplitude, and Mixpanel out of the box, in the real shape each one exports, including the nested-JSON formats some of them wrap each event in rather than a pre-flattened CSV. It recognizes the source on sight and labels the import for you. Any other CSV or JSON export still works; the generic path falls back to header regex plus value-pattern matching, and the confirmation step lets you fix anything it got wrong.
Heatmaps are powered by a single event convention: emit player_pos with a payload of { x, y, room } on whatever cadence makes sense for your game (every fixed tick, every N frames, on level entry, etc.). The aggregator buckets points into a 32×18 grid per room, log-scales the intensity (so one stuck-in-doorway session doesn't wash out the rest of the map), and renders a density plot per room on the game dashboard.
The contract is identical across every SDK, only the syntax changes. Pick the snippet for your runtime:
// In FixedUpdate, or on a 10–20Hz coroutine…// pick a rate that matches your map scale.PlayloopBootstrap.Track("player_pos", new Dictionary<string, object> { { "x", transform.position.x }, { "y", transform.position.z }, // top-down: use z for "y" { "room", currentRoom.id },});# In _physics_process(delta) or a Timer at 10–20Hz…PlayloopBootstrap.client.track("player_pos", { "x": global_position.x, "y": global_position.y, "room": current_room.name,})# Useful for replay bots that walk a transcript and# stamp the path in playloop after the fact.client.track("player_pos", { "x": x_world, "y": y_world, "room": room_id,})// Phaser / PixiJS / WebGL canvas, call on each tick// or on a setInterval at 10–20Hz.playloop.track('player_pos', { x: player.x, y: player.y, room: scene.key,})Payload rules: and must be finite numbers in your game's world space (any unit, the aggregator finds the bounds per room). must be a non-empty string and should be stable across sessions for the same logical area. Events missing any field are silently dropped. Cap on rooms returned per game: 12 (highest-traffic first).
All five SDKs are thin wrappers around POST /api/telemetry. You can hit it directly with curl if you want, useful for CI bots, server-side game logic, or any runtime we don't have an SDK for yet.
POST /api/telemetry HTTP/1.1Host: playloop.ggAuthorization: Bearer pl_ik_<hex>X-Playloop-Environment: demoX-Playloop-SDK: unity // unity | unreal | godot | python | typescriptContent-Type: application/json { "sessionId": "sess_…", // omit on first flush of a launch "deviceId": "<stable-device-id>", // first flush only "sessionMetadata": { "gameVersion": "0.4.9", "platform": "OSXEditor" }, "events": [ { "id": "01HX…", // client-supplied dedup id (1..24 chars) "name": "level_completed", "timestamp": 1778714850693, "data": { "level": 3, "deaths": 0 } } ], "sessionEnded": true // flush + trigger AI analysis}The server dedupes events by id(per-session FIFO ring of the last 1000 seen), so retried batches don't double-write. sessionEnded:true is the only signal that kicks the analyzer, flushes without it just append to the open session.
The header tells Playloop which SDK is calling, accepted values are , , , , and . The Integrations page uses it to attribute sessions to the right SDK card; missing or unknown values fall back to for back-compat with pre-header SDK builds.
The standard two-key split: a public-by-design key for the client + a server-only key for management. Pick the right one for the right surface; a leak in one never compromises the other.
Write-only, authenticates /api/telemetry and nothing else. Can't read sessions, delete data, or rotate other keys. Rate-limited per key + per IP. If it leaks via decompilation: hit Rotate on /settings, drop the new value into your SDK secret, rebuild.
Full scope, reads sessions, deletes data, manages webhooks, rotates keys. Use it from CI scripts, your backend, or admin tooling. Never ship in a game binary, a decompiled binary leaks any string it contains; this one would give full account access. Management API + MCP access is free at every tier, rate-limited per management key. Paid tiers (Indie, Studio) get higher per-key throughput.
Read the Management API reference — full endpoint list, query params, response shapes, and curl / Node / Python examples.
Manage endpoints from Integrations → Webhooks. Playloop POSTs to each endpoint whenever a subscribed event fires. Deliveries are HMAC-SHA256 signed (X-Playloop-Signature, hex-encoded) with the secret returned once at create time. Verify with your SDK's built-in helper, pure HMAC, no I/O.
POST with application/json. The body is the event payload, no envelope wrapping. The event kind comes from the subscription you registered, plus (forsession.analyzed/session.failed) an event field inside the body.Nine subscribable events grouped by lifecycle. Subscribe to any combination from the endpoint editor at /integrations/webhooks.
Upload your Steam / Epic / itch keys, or have Playloop mint server-validated beta keys for you. Testers redeem on a public, no-signup page; if you wire up linkTester in your game, the SDK correlates each redeemed key to the tester's in-game sessions so you see who played what. Free for testers, always, the redemption page carries a hardcoded anti-scam banner you can't turn off.
/games/[slug]/playtest). Pick a distribution target (Steam, Epic, itch, GOG, Keymailer, Woovit, Playloop-gated, or custom), a redemption mode (public share link or 1:1 email invites), and what you ask testers for (handle only / handle + email / no identity).pl-betakey-XXXX-XXXX-XXXX tokens for closed betas, those validate via POST /api/v1/playtest/validate when your game launches./redeem/<token> URL anywhere, Discord, your itch page, a giveaway tweet. For 1:1 invites, paste emails and we send one-shot tokens.RedFox42), optionally enter an email, and get two things: the game key and a pl_tt_… tester token.linkTester; from that point on every telemetry event auto-tags with the tester's handle in your dashboards.Connect Claude Desktop, Claude Code, Cursor, or Codex CLI to your Playloop account via the Model Context Protocol. Your agent gets typed tools for list_games, get_session, query_insights, list_playtest_batches, and fifteen more, read-only, scoped to your management key.
Drop this into your client's MCP config. No install. Replace pl_mgmt_<your-key> with your management key from Settings → API keys.
{ "mcpServers": { "playloop": { "url": "https://playloop.gg/api/mcp/sse", "headers": { "Authorization": "Bearer pl_mgmt_<your-key>" } } }}Four toggles on Settings → Notifications choose which emails you get: analysis-finished, integration errors (webhook auto-disabled), weekly digest, and tester invites. Default ON. You get a bundled email within ~13 minutes of the first session in an analysis burst, so a back-to-back run of analyses lands as one email instead of ten. The weekly digest ships every Friday at 14:00 UTC with week-over-week delta chips on sessions, testers, and insights.
Every sensitive action on your account (sign-in, key rotation, BYOK key save, password change, TOTP enroll, OAuth link, step-up reverification, account-close attempt) writes one newest-first row to Settings → Audit log. Webhook auto-disable kicks in after 20 consecutive failures inside a 24-hour window.
See /docs/notifications for the deep dive , full toggle reference, bundle math, weekly-digest delta rules, and the audit-kind catalog.
Still missing something? , we're a small team, emails get read.
No. The SDK posts directly to POST /api/telemetry on playloop.gg. Sessions, insights, summaries, and exports all live on our side. The only thing that ships in your game is the SDK + your Ingest key.
No. track() appends to an in-memory buffer synchronously; actual HTTP flushes happen on a background 5-second loop (AutoBatch). Even spamming track() ten thousand times per session is just array pushes on the main thread. Network and JSON serialization happen off the hot path.
Engine-specific install steps, working first-event snippets, and the "what you get for free" list for each SDK (heartbeat, focus tracking, bounded shutdown).
We're a one-person team during the beta, emails get read. Sharper bug reports + feature requests get faster answers.
These docs are evolving. Playloop is in active development ahead of launch, so APIs and details may change as we polish.
Compare side-by-side. See which friction faded, which milestones got harder, whether the patch landed.
“7 testers said variations of: ‘I thought the door was locked.’”
Playloop groups the language. You see one row that says “locked door confusion (7 testers)” instead of seven scattered comments you’d never connect.
Track() call you sprinkle on the events that matter (boss defeated, level complete, settings opened, whatever your game already knows about).The implementation checklist below is the same flow with real code. Read top-to-bottom or skip to the SDK install for your engine.
Events your game already knows about, batched and sent in the background:
The other side of the same pipe, what Playloop hands back once sessions land:
Sign up (free during the beta), then create a game from the Games tab. The game's slug shows up in your dashboard URL, /games/your-game.
Go to Settings → API keys and copy the Ingest key. This is the write-only credential that goes in your game. Never use the Management key in a client, it's server-side only.
Per-engine instructions live on the SDKs page. The short version for each runtime:
# Unity, Window > Package Manager > + > Add from git URLhttps://github.com/playloop/sdk-unity.git# …OR pin it in Packages/manifest.json:# {# "dependencies": {# "gg.playloop.sdk": "https://github.com/playloop/sdk-unity.git"# }# } # Unreal, clone the plugin into your projectgit clone https://github.com/playloop/sdk-unreal.git Plugins/PlayloopSDK# Then regenerate IDE project files and rebuild. # Godot, copy addons/playloop/ into your projectcp -R sdk-godot/addons/playloop res://addons/playloop # Pythonpip install playloop # TypeScriptnpm install @playloop/sdkOne init call, one track(), and Playloop takes care of batching, heartbeats, focus tracking, and bounded shutdown for engine SDKs.
Security: read the key from environment variables, never hardcode it. In TypeScript/Node use process.env.PLAYLOOP_INGEST_KEY; in Python use os.environ["PLAYLOOP_INGEST_KEY"]. Add .env to your .gitignore so committed code never carries a live key. Leaked ingest keys are rotatable from Settings , but prevention is one line of config.
import { Playloop } from '@playloop/sdk' const client = new Playloop({ apiKey: process.env.PLAYLOOP_INGEST_KEY!, baseUrl: 'https://playloop.gg', environment: 'demo', // optional · default 'production'}) await client.startSession({ build: process.env.GAME_VERSION }) client.track('level_completed', { level: 3, deaths: 0, durationSec: 142,}) await client.endSession() // flushes with sessionEnded:trueOpen the dashboard , your session appears in seconds. Once analysis runs you get AI-extracted insights per session, then summaries per tester (device), per build (game version), and event aggregates at every level.
2FA, device list, sign-out-everywhere, the danger zone.
The engine-agnostic SDK contract plus the editor-side tools and the troubleshooting guide every consumer eventually needs.
The four canonical events, the state accumulator (setState, incrementState), heartbeat cadence, auto-flush guarantees per runtime.
What the Unity, Unreal, and Godot SDKs add to your editor: top-level Playloop menu, Send Test Event, env switcher, quick links to your dashboard.
Common failure modes (401 / 403 / 404, heartbeat clamp warning, events not arriving, gameSlug typo, force-quit, offline retry).
HTTP-level access for studios who want to script Playloop, pipe it into other tools, or talk to it from AI agents.
Identified by a stable device hash (SystemInfo.deviceUniqueIdentifier in Unity, OS.get_unique_id in Godot). Roll up every session this person played, with their own AI summary.
A free-form slug stamped on every session, production, demo, staging-eu, whatever. Auto-created on first use. Use it to keep playtest data separate from real player data.
A typed observation pulled from the session: stuck-point, confusion, praise, bug, pacing-issue, session-summary. Cited with timestamps + event evidence.
Every SDK is a thin wrapper around the same JSON contract, Bearer auth, environment header, dedup by client-supplied event id. Useful for engines without an SDK yet (Unreal, GameMaker, custom C++), CI runners, server-side game logic, or anywhere you can curl.
The Integrations sidebar also lists OBS and Steam Playtest, those pages document how to wire those tools to one of the four paths above (OBS captures video → uploads via /sessions/new; Steam playtests flow through Discord). There's no dedicated OBS/Steam endpoint yet, we'll add direct ones if usage justifies it.
High-frequency events (click, hold, heartbeat) collapse into per-minute density bins. Meaningful events stay verbatim with their first/last timestamps + counts. AI sees the narrative arc of the session in a few thousand tokens instead of 300k.
Settings → Events lists every event name the SDK has sent. Per event you set AI visibility (hidden / aggregated / verbatim), metric kind (counter / unique / first / last), and an optional one-line description the AI uses to understand what the event means in your game.
A 2KB plain-English textarea on each game's settings ("Necromancer's Army is an idle clicker; the demo ends after the act 2 cinematic; players who don't reach act2_begin are early drop-offs") prepends to every AI prompt for that game. Sharper summaries without per-prompt babysitting.
Every SDK that has access to a focus signal emits focus_lost / focus_gained when the player alt-tabs or backgrounds the app. The server subtracts those idle gaps from wall duration. "They had it open 42 min, played 18 min" tells a different story than just "42 min".
Emit player_pos events with { x, y, room } and the dashboard renders a per-room density grid. 32×18 cells by default (clean 16:9). Log-scaled intensity so a handful of stuck-on-the-loading-room sessions don't wash out the rest of the map. See the Heatmaps section below for SDK snippets.
Country / region / city stamped at ingest from edge-network geo headers. Environments (production / demo / staging) are free-form slugs you set per-build, auto-created on first use, dashboard filters by env on every view.
Session-level JSON (events + insights), tester-level JSON (lifetime sessions + summary), build-level JSON (rollup + per-tester summaries) and a flat CSV (one row per session for spreadsheets). All synchronous downloads; no email-link queue.
Every AI summary block has a "View what the AI saw" expander showing the compacted digest + the system prompt (with your AI context prepended) verbatim. Click-to-copy on both, debug bad outputs without guessing.
Pick any of the three on Settings → AI provider, paste a key, choose a model. The model dropdown pulls live from the provider when a key is saved, deprecated models disappear automatically, new ones appear without a redeploy. Keys encrypted at rest (AES-256-GCM), decrypted only at the moment of a model call.
AI provider docs →Pick a build (or a build-diff) and the AI returns ranked, concrete fixes grounded in the tester sessions that exposed the friction. Each suggestion cites the source insights so you can verify before you act. Available via the dashboard, the /api/v1/builds/compare/suggestions endpoint, and the suggest_fixes MCP tool.
Connect a PAT in Settings → Integrations (no agent to install). Every friction insight gets a "Create issue" button that drops the title, body, build, and a deep link back into your tracker. Ticket-id + URL are stored on the insight so you don't double-file.
Drill into a single tester to see every session in chronological order, each with their top friction, top praise, and the build version that produced it. An engagement-trend tag (rising / flat / declining / insufficient_data) is computed from the first-3 vs last-3 sessions average, see exactly when a tester started to fall off.
Wire the Slack Events API to /api/webhooks/slack. Every signed message in the configured channel appends to an open session transcript (5-minute window grouping). HMAC signature verified before any token is spent. Free testers get notified via bell + email when their session was skipped because of BYOK rules.
Take every tester's narrative summary, embed it (text-embedding-3-small / text-embedding-004), greedy-cluster by cosine similarity, label each group. "Optimizers," "completionists," "breakers", and which friction tags each group hits hardest. Requires a BYO embedding key; returns insufficient_data when fewer than 5 testers have summaries.
Define a form on the Feedback tab: any combination of 1-5 rating, yes/no, short text, and long text fields. Game code calls client.feedback.submit({ formId, sessionId, responses }), answers come back grouped as one submission per call, tagged to the session and the build. Engine SDKs (Unity, Godot, Unreal) ship a built-in default form you can spawn with one line, or bring your own UI. Two hardcoded spam caps protect the player: one submission per (session, form), three submissions max per session across all forms. Free for every studio.
Player Feedback docs →Split players across two or more variants, ship, and read a side-by-side comparison of retention, engagement, crashes, and friction per variant, with calibrated confidence labels and an AI digest of what changed. Assignment is server-side and deterministic by device, so every SDK agrees; structured comparison is free on every tier.
A/B experiments docs →Every SDK installs a crash trap on construction. Uncaught exceptions persist locally, flush on the next session start, group by stack signature. Browser stacks resolve automatically when you upload source maps; Unity IL2CPP gets a dev-side Editor window with bulk submit + heuristic auto-fill.
Crash reports docs →Every Playloop SDK fires a session_summary event with a top-level snapshot of game state at the moment the run closes (final score, ending reached, key flags). Playloop pulls that payload verbatim into a structured "Session state" block at the top of the digest the AI reads, so end-state run data lands as primary signal instead of getting buried in event spam. Imported or custom data whose end-of-session event has a different name (custom playfab_session_end, run_complete, etc.) maps to the same block by naming your summary event in Settings → Events. A recurring snapshot event (a heartbeat) is read as a trajectory there too.
Transcripts without parseable event lines (Slack channel exports, Discord webhook posts, hand-pasted notes) used to fall back to "no events". Playloop now inlines the raw content into the digest with a 16KB head-plus-tail truncation marker for very large bodies. The AI reads what's actually there instead of giving up.
A long-standing parse bug on Windows-style transcripts (CRLF instead of LF) is fixed. If your CI pipeline or a Windows uploader produced transcripts that read as one giant line, they now break apart correctly and the event timeline reconstructs as expected.
Where to find Force re-analyze: every summary surface (session detail, build detail, tester detail, game overview) has a Regenerate button. Hold Shift while clicking, or pick “Force re-analyze” from the dropdown, to bypass the cache.
The dashboard drives these through internal session-cookie routes. For programmatic access from a backend, use the Management API.
Your current plan, payment method on file, past invoices. During the beta everything is free, so this tab is mostly informational; when the Indie and Studio plans launch the upgrade path lives here.
Three rules, picked in priority order:
The second import from the same source is one click. Playloop stores the column mapping per game by source-fingerprint, so the same PlayFab export shape next month skips Step 2 entirely. Edit the saved mapping any time from the import dialog.
Small imports (up to five sessions) auto-fire the analyzer right after the rows land, you’ll see AI summaries start streaming in without an extra click. Larger imports leave that toggle off by default so a 1,000-session backfill doesn’t accidentally spend a fortune; turn it on in the confirm dialog if that’s what you want.
SDK telemetry sessions now store their events as structured JSON alongside the human-readable transcript. Nothing changes for you as a user, the dashboard, exports, and the AI digest pick whichever source has higher fidelity automatically. The structured column is what lets the “Session state” block surface session_summary and session_heartbeat snapshots with full precision instead of the 80-character truncation the text path applies.
The dashboard drives bulk import through an internal session-cookie route. For programmatic ingest from a backend, use the SDK or the Management API.
xyroomCadence: 10–20 Hz is usually plenty. Higher rates produce denser maps but your player_pos event will dominate your event count (and your AI digest unless you mark it aggregated in Settings → Events).
X-Playloop-SDKunityunrealgodotpythontypescriptunitysession.createdFires for every new session row, telemetry batches, uploaded recordings, Discord webhook posts, Slack channel messages.
{ "sessionId": "sess_mp4nv055s6oepmc6", "gameId": "game_b3k7...", "source": "telemetry", "testerHandle": "RedFox42", "recordedAt": 1730000000000}session.analyzedThe AI pipeline finished and insights are attached. Severity is derived from sentiment (negative → high, neutral → medium, positive → low) so consumers can filter without knowing Playloop's internal sentiment vocabulary.
{ "event": "session.analyzed", "session_id": "sess_mp4nv055s6oepmc6", "duration": "1754", "insights": [ { "type": "stuck-point", "severity": "high", "confidence": 0.81 }, { "type": "praise", "severity": "low", "confidence": 0.93 } ]}session.failedAnalysis failed, most often a missing transcript or transcoding error.
{ "event": "session.failed", "session_id": "sess_mp4nv055s6oepmc6", "error": "No transcript available"}insight.createdOne per inserted insight row. High volume, an analyzed session emits 5–20 of these. Filter on confidence /sentiment /type at your receiver.
{ "sessionId": "sess_mp4nv055s6oepmc6", "insightId": "ins_aw82...", "type": "stuck-point", "sentiment": "negative", "confidence": 0.81, "tags": ["combat", "boss-room"]}build.summary.readyThe build-level AI summary for a (game, version, environment) tuple was refreshed. The payload carries a 280-char preview, fetch the full summary + top friction list via the management API.
{ "gameId": "game_b3k7...", "version": "0.5.0-beta", "environment": "playtest", "summaryPreview": "Testers consistently stalled at the boss arena...", "sessionCount": 12, "userCount": 8}tester.linkedA tester's claim token was bound to a device for the first time, telemetry from this device now auto-tags with the picked handle.
{ "gameId": "game_b3k7...", "handleId": "hnd_qz9k...", "handle": "RedFox42", "deviceId": "dev_8fa2...", "claimedAt": 1730000000000}playtest.key.redeemedA tester claimed a key from one of your batches. The cleartext key, claim token, redemption IP, and reporter email are intentionally not sent, keep them server-side and pull them from the management API if you need them.
{ "batchId": "btc_aw82...", "gameId": "game_b3k7...", "keyId": "key_qz9k...", "handle": "RedFox42", "country": "US", "distributionTarget": "steam"}playtest.batch.exhaustedThe final key in a batch was consumed. A good signal to mint or upload another wave.
{ "batchId": "btc_aw82...", "gameId": "game_b3k7...", "name": "October friends & family", "keyCount": 50, "redeemedCount": 50}playtest.scam_reportedA tester reported being asked to pay for one of your keys. Free-form complaint text and the reported URL stay server-side, the payload says "something happened, go check the dashboard."
{ "batchId": "btc_aw82...", "gameId": "game_b3k7...", "paymentDemandPresent": true, "country": "US"}Skip this section entirely if you just want keys distributed, the redemption flow works without any SDK changes. If you DO want the studio to see which tester played what, drop these four lines into your main menu:
const token = await prompt('Playloop tester token (optional):')if (token) { await client.linkTester({ claimToken: token })}Same shape in Python (client.link_tester), Unity (await client.LinkTesterAsync(token)), and Godot (await playloop.link_tester({...})). Pair with getCurrentTester() and unlinkTester()for a “Playing as RedFox42 / Switch tester” menu UX.
Playloop NEVER charges testers for a key. The redemption page, the post-claim success screen, and the confirmation email each carry a non-removable warning + a one-click report link.
When a tester submits a scam report against your batch, three things happen the moment they click submit:
We never auto-suspend studios. Almost every report is either a reseller acting without the studio's knowledge, or a misunderstanding. The studio email is a security signal that bypasses your tester invites notification preference, you can't mute it, by design.
The feature is free on every tier, no per-key fee, no monthly cap, no metering on testers. Deeper distribution analytics, per-tester drill-down, a cross-country flag for likely-shared keys, the branded redemption page, CSV key import, and per-batch session exports, are on the roadmap and not yet shipped. The redemption experience itself is identical on every tier, that's deliberate.
Deeper reading: Studio guide · SDK correlation contract · What testers see.
If you'd rather not point at a hosted URL, run the npm package locally via npx.
{ "mcpServers": { "playloop": { "command": "npx", "args": ["-y", "@playloop/mcp", "--key", "pl_mgmt_<your-key>"] } }}Use a management key (pl_mgmt_…), required for read access. Ingest keys (pl_ik_…) are explicitly rejected with 403 requiredScope: "management" to prevent accidental leakage of a write-only key into a read-everything context.
Analytics
list_games · get_game, your games + countslist_sessions · get_session, filtered sessions + per-session detail with insightsquery_insights, sentiment / type / time-range filteringget_build_summary · compare_builds, per-version rollups + diffget_tester_summary, per-tester rollup + AI summaryget_heatmap, per-room density gridsget_event_stats, event-count aggregatesPrescriptive & per-tester (six-features pass)
suggest_fixes, AI-ranked concrete fixes for one build OR a build-diffget_tester_journey, one tester's full session-by-session arc + engagement trendlist_tester_archetypes, embed-and-cluster behavior groups (optimizers / completionists / breakers)list_game_feedback_forms, studio-defined Player Feedback forms (with fields + per-form submission counts).Distribution
list_playtest_batches · get_playtest_batch, bundle metadata + key/redeem/revoke countslist_playtest_keys, per-key lifecycle + redemption metadata (no cleartext, no IP)list_tester_invites, 1:1 invite send / open / redeem timeline (no token)list_playtest_handles, SDK-linked testers + rollup (sessions, playtime, distinct-country abuse signal)weekly_digest, last 7 days, what shipped + what hurtbuild_comparison, diff two versions side-by-sidefriction_analysis, negative-sentiment patterns by scopetester_spotlight, top-engaged testers reportSource for both transports (stdio CLI + hosted SSE), tool definitions, and the four pre-canned prompts. Issues and PRs land here.
Want the raw HTTP? Read the Management API reference , same surface MCP wraps, exposed as twenty-one REST endpoints you can curl, fetch, or hit from a data pipeline.
Yes, it's designed for that. Same model as any public client-side ingest token. The Ingest key is write-only: it can post telemetry events but can't read sessions, delete data, or rotate other keys. Rate-limited per key + per IP. If it ever leaks, hit Settings → Rotate and the old key dies instantly.
The Management key is the one to keep server-side only, never bundle it in a client.
Events stay in the in-memory buffer and retry on the next flush. If the player force-quits while offline, the unsent batch is lost, that's an acceptable trade for not writing telemetry to local disk on every event.
For long offline scenarios you can call flush() yourself at meaningful moments (level boundaries, save points) to make the loss window smaller.
Engine SDKs auto-resolve a stable device ID from the platform (SystemInfo.deviceUniqueIdentifier on Unity, OS.get_unique_id() on Godot). The same machine collapses to one tester across rebuilds, fresh installs of your game, and PlayerPrefs resets.
The ID is anonymous (a platform-derived hash, not raw hardware IDs). If you want explicit override, e.g. tying a session to a known QA tester, pass deviceId explicitly to the SDK options.
Yes, use the environment option. Stamp playtest builds with environment: "demo" (or "staging", or whatever slug you like) and release builds with environment: "production". The dashboard filters by env on every view; rollups and AI summaries are computed per-env so playtest noise never pollutes your release stats.
No. Raw event counts, playtime stats, top events, retention, and engagement rate are all SQL, zero AI involved. The AI summaries layer on top once you want narrative interpretation ("this build introduced a stall at story_beat_3 that 4 of 5 testers hit") instead of just numbers.
AI summaries regenerate automatically when new sessions land. You can also hit Regenerate on any summary block to force a fresh pass.
A compaction pass converts the raw transcript into a digest before any AI call. High-frequency events (click, hold_engaged, heartbeat) become per-minute density bins; meaningful events keep their first/last timestamps + counts. A real session that produces 21,269 events and 4.7MB of transcript compacts to ~3KB of digest with the narrative arc intact.
Each AI summary block has a "View what the AI saw" expander showing exactly what was fed to the model, so you can debug any output without guessing.
Yes, all three. Head to Settings → AI provider, pick OpenAI / Anthropic / Google Gemini, paste a key (sk-… for OpenAI, sk-ant-… for Anthropic, AIza… for Gemini), and pick a model from the dropdown. The dropdown pulls the available model list live from your provider when a key is saved, so deprecated models drop off automatically and new ones appear without a Playloop redeploy. Every AI op in that workspace routes through your chosen provider; you pay the provider bill.
All keys are encrypted at rest (AES-256-GCM) and decrypted only at the moment of an AI call. Clear any of them from the same settings surface. During the beta on the Free plan, AI needs a BYO key (your provider, your bill). Without one, analyze still runs, just deterministically: the session lands with a structural summary built from raw events, no model call, no status="failed". Add a key any time to upgrade future runs to AI narratives. When the Indie and Studio plans open, managed AI ships alongside them and BYO will still work on top of the quota.
If your chosen model is retired (e.g. OpenAI rolls forward from gpt-5-minito a new mini), we detect it before the next AI call and switch your workspace to the closest available model in the same tier, mini stays mini, flagship stays flagship, nano stays nano. We never auto-upgrade to a more expensive tier; downgrade is allowed when no same-tier match exists. You get an in-app notification telling you exactly what changed and a link to Settings if you'd rather pick something else.
See /docs/ai-provider for the full picker contract, provider radios, the live model catalog endpoint, denylist rules, monthly spend cap, and the step-up gate on every key change.
Audio + video uploads run through OpenAI Whisper. Whisper is OpenAI-only, Anthropic and Gemini don't host an equivalent endpoint, so your provider choice for the insight extractor doesn't affect transcription.
During the beta: paste an OpenAI key on Settings → AI provider and Whisper is billed to your OpenAI account. Without one, transcription returns a fail-soft message; the session still ingests but the transcript is just the message text.
Whisper picker, per-minute pricing (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe), the 60-min/call + 8-hr/day managed caps, and the Whisper-only BYO key field are documented on /docs/ai-provider → Transcription.
Free: bring an OpenAI / Anthropic / Gemini key and your provider account is the meter on analysis of incoming sessions, with a generous daily cap on the analyze actions you trigger by hand (see Analysis). Without a key, sessions still land and get a structural summary built from raw events (event counts + percentages, durations, positive/negative-moment detection from event names). Adding a key any time upgrades future runs to AI narratives, friction clusters, and quote extraction.
Paid plans are coming soon. Indie and Studio tiers will add managed-AI quotas (no BYO key required), longer retention, and team collaboration. Open-beta accounts will get a heads-up before they ship; nothing about your Free experience changes when they do.
Device IDs are hashed by the OS / SDK before they reach Playloop, we never see raw hardware identifiers. Geo is derived from IP at ingest (country / region / city) but the raw IP itself is not stored. You can delete a session (and its insights + summaries) any time from the dashboard, and close your account self-serve from Settings, there's a 14-day soft-delete window where you can undo, then an automated daily cleanup permanently deletes the account and every game, session, transcript, insight, and uploaded asset you own.
Whatever event payloads you send are stored as you sent them, keep PII out of the data field on individual events unless your players consented to it specifically.
Unity (UPM package), Godot 4.4+ (GDScript addon), Python 3.10+ (PyPI), and TypeScript / JavaScript (npm, works in Node, Bun, Deno, edge runtimes, and browsers). See the SDKs page for install + first-event examples.
Other engines can hit POST /api/telemetry directly, the SDKs are thin wrappers around the same JSON contract.
Free during the beta. Bring your own OpenAI / Anthropic / Gemini key, your provider account is the meter on ingest analysis, with a generous daily cap on the analyze actions you trigger by hand. The dashboard, telemetry ingest, AI summaries, exports, heatmaps, and tester-key distribution are all included.
Indie and Studio are coming soon. When they land they'll add a managed-AI quota (no key needed), longer retention, more seats, and more storage. The Management API, MCP server, and outbound webhooks are already free on every account. Fixed-cost tiers, no per-event metering, no revenue share, ever. See the pricing page for the current state.