Quickstart
Sign up to create your studio, add a game to mint a per-game SDK token (tmb_…), then initialize the SDK once. Managed exceptions and session heartbeats are captured automatically. Players are never anonymous: the SDK mints a persistent salted device id (dev_…) and, once SetUser runs, the session's pre-auth data merges into the real player id.
Core concepts
Four primitives power everything in Tombstack:
Install & init
Add the UPM package com.anklebreaker.tombstack in Unity 6 (6000.0)+ — download the .tgz from the download page and use Window ▸ Package Manager ▸ + ▸ Add package from tarball…:
Grab the latest .tgz from the download page (it always serves the current release).
Prefer git? Add package from git URL… works for everyone — the package lives in a public repository: https://github.com/AnkleBreaker-Studio/tombstack-unity.git#v0.19.1. Use the tarball instead if you enable Require Consent — the git mirror is still at v0.19.1, which predates the v0.19.2 fix for consent granted before initialization. On v0.19.1, a build with Require Consent ticked that grants consent early can report nothing at all. Zero-code init: create a Tombstack ▸ Config asset under a Resources/ folder named TombstackConfig — it auto-initializes on load.
What's automatic
After Tombstack.Init (or zero-code auto-init), the SDK is fully autonomous — no try/catch wiring, no log-shipping code:
Every crash report carries an optional kind so the dashboard stops calling every report a "crash" — the Graveyard has a Kind filter and labels each grave distinctly:
The field is optional on the wire — older SDKs omit it and the server derives the kind, so it's fully back-compatible.
Retain more or fewer launch logs via TombstackConfig ▸ Retained Launch Logs (or Init(…, retainedLaunchLogs: N), 1–10; default 3).
Logs surface in the dashboard as Player log download links on the signature detail and bug detail pages, and GDPR erasure deletes them with the player's other artifacts. All three systems are config toggles (default ON) and consent-gated: with Require Consent, nothing is captured until Tombstack.SetConsent(true).
Capturing crashes
Managed C# exceptions and session heartbeats upload automatically; failed uploads persist and retry on next launch. Capture handled exceptions explicitly to keep context.
Session heartbeats fire every 60 s (they drive live CCU + the crash-rate denominator) and carry frame stats — including a per-~20 s fpsSamples series folded into the beat, so the dashboard sees sub-beat FPS without extra rows. The SDK also sends an on-demand beat when the app is backgrounded (minimized) and, best-effort, on quit, so liveness stays tight. Force one at any lifecycle moment with Tombstack.SendHeartbeatNow() — gated exactly like the periodic loop (consent + heartbeats enabled + session started) and de-duped server-side, so it never double-counts CCU.
Player bug reports
Let players file bug reports from inside the game — optionally with a screenshot. They land in the dashboard alongside crashes.
Analytics events
Track gameplay analytics with one-liners. Events accept flat string attributes (≤32 keys) that power the event browser, per-key rollups, player timelines and funnel conditions; metrics get time-series with p50/p95/p99. Both are batched client-side (flush on count ≥ 50 / age ≥ 10 s / pause / quit) and calls made before init are buffered (64, drop-oldest) and replayed with their original timestamps once the SDK initializes.
Typed helpers (SDK 0.10.0+) emit standard tmb.* events — plain custom events underneath, so batching, funnels and attribute filters all apply — that the dashboard auto-recognizes: tmb.progression events render an automatic level-progression table (starts / completes / fails / fail:complete / completion %) on the Analytics page, with TrackEconomy, TrackPurchase and TrackAdImpression covering economy, IAP and ads.
Environments
Every payload carries an environment label (default production) so one game can hold production / staging / development builds without mixing data. Set it zero-code via the Environment field on the TombstackConfig asset, or in code — an explicit SetEnvironment always wins over the config value, even when called before init. Environments are free-form (≤64 chars) and self-registering — the first heartbeat from a new label makes it appear in the dashboard's environment selector. You can also create, rename, or delete labels from the dashboard before any build reports. Every page, CSV export, the read API (?environment=) and the MCP tools filter by it.
Authentication
All endpoints authenticate with a token sent as Authorization: Bearer … (or x-api-key). Two kinds exist:
The public read and triage APIs require a studio key (tmb_st_…) or a per-game token that was granted the read/write scope. A default ingest-only SDK token is rejected with 403; a missing or unknown token gets 401.
/api/v1/studio/gamesStudio key only — list the studio's games (for fan-out)Every response uses the envelope { "success": true, "data": … } / { "success": false, "error": "…" }. Common error codes: 400 validation, 401 bad/revoked token, 404 not found, 429 rate limited (with Retry-After).
Rate limits (fixed 1-minute window). These are the ingestion limits and do not apply to the read API — see the note below, which an earlier version of this page omitted, so an integrator sizing a poller against 20,000/min would have started getting 429s at 600. 120 requests/min per IP — the abuse guard for a crash-looping client — and a deliberately high 20,000 requests/min per game key backstop, so a real crash spike across your whole player base is never throttled.
The read API (/api/v1/read/*) is bounded far lower, because each call is an aggregate query rather than a single row write: 300 requests/min per IP and 600/min per key, same 1-minute window. Size dashboards and CI pollers against those numbers, not the ingestion ones. A per-IP gate is also applied before your key is verified, so a burst of invalid keys is rejected without touching the database.
A separate editor API (/api/editor/*) powers the Unity plugin's in-editor hub — it is plugin-internal, documented in the repo's docs/API.md.
Ingest API
Game clients are treated as hostile input: every field is validated, clamped and rate-limited. os ∈ {windows,macos,linux,android,ios,other}, arch ∈ {x64,arm64,x86,other}, timestamps within the 90-day window.
/api/v1/ingest/crashes201 { crashId, logUpload? }/api/v1/ingest/heartbeats202 { accepted }/api/v1/ingest/bug-reports201 { bugId, logUpload? }/api/v1/ingest/events202 { eventId }/api/v1/ingest/metrics202 { metricId } — single numeric metric/api/v1/ingest/events:batch202 { accepted, dropped, skewMs }/api/v1/ingest/metrics:batch202 { accepted, dropped, skewMs }High-frequency telemetry batches via { sentAtIso, items: [...] } (1–200 items, ≤512 KB). Each item is validated independently — a bad item is dropped and the rest stored — and carries its own occurredAtIso; the envelope's sentAtIso only computes clock skew. A batch is charged its item count against the rate window, so batching can't amplify ingest past the per-IP/per-key limit.
Events accept an optional attributes map (≤32 entries; keys ≤64 chars; values string ≤512 chars, number, or boolean). Attributes surface in the dashboard's event browser, per-key rollups, and funnel conditions, and are returned by GET /api/v1/read/events.
Binary artifacts upload out-of-band via presigned S3 multipart POSTs (15-min TTL) requested with boolean flags: crashes accept "minidump": true (response carries data.minidumpUpload) and bug reports accept "screenshot": true (data.screenshotUpload). Both accept "log": true to request a player-log slot (data.logUpload, text/plain). Read APIs return a short-lived logUrl wherever a log exists.
Crashes accept an optional kind ∈ crash | exception | unclean_shutdown so the dashboard labels each report accurately (and the Graveyard can filter by it). Omit it and the server derives the kind — fully back-compatible.
Read API
Pull aggregated and recent data back out — for dashboards, automations or your own tooling.
/api/v1/read/crashes/summary?days=30Aggregated/api/v1/read/crashes?days=7Recent rows/api/v1/read/signatures?days=30All signatures + triage status/api/v1/read/signatures/{signature}Drill-down + trend + logUrl/api/v1/read/bug-reports?days=30Recent reports/api/v1/read/bug-reports/{bugId}?at=…One report + screenshot/logUrl/api/v1/read/events?days=7&name=level_complete&userId=user-123Recent events incl. attributes — name filters one event, userId scopes to one player/api/v1/read/players/{userId}/crashesOne player's crashes/api/v1/read/usageCCU peak, plan, est. USD/api/v1/read/matches?days=7Derived matches — span, players, server, crash count/api/v1/read/matches/{matchId}One match's full telemetry timeline/api/v1/read/metrics?name=fps&days=7Series + percentiles (groupBy serverId|matchId|buildVersion|os)/api/v1/read/retention?days=30DAU/WAU/MAU + D1/D7/D30 cohort (player rows only; format=csv supported)/api/v1/read/retention?by=osSegmented cohorts — by os|platform_class|device_model|gpu_vendor|gpu|ram_band|vram_band|cpu_cores|engine|country (format=csv → one row per segment)/api/v1/read/servers?days=7Fleet list — live CCU, crash-free, last seen/api/v1/read/servers/{serverId}Server detail — metadata, health, recent crashes/api/v1/read/servers/{serverId}/connected?days=1Players connected to the server in the window/api/v1/read/pull-requestsLog-pull status + audit trailLive sessions — the dashboard's "Live now" panel pages through currently-live player sessions (last beat inside the ~10-minute live window). It is session-cookie authed (any studio member), not a token route:
/api/v1/read/games/{gameId}/live-sessions?environment=&platform=&offset=&limit=Paginated live sessions — offset (≥0) / limit (1–100, def 25) + env/platform filtersRetention cohorts can also be split by ?by=country — the viewer country is server-derived at the edge (CloudFront viewer-country header, with a GeoIP fallback on the client IP), never sent by the SDK, so per-country cohorts work with no game-side field. Unknown IPs fall into an (unknown) bucket.
The five list/summary routes (crashes, crashes/summary, signatures, bug-reports, events) accept an optional ?environment= filter — production also matches legacy rows written before the environment feature, all matches everything, and omitting the param returns all environments.
Six read routes (crashes, crashes/summary, events, metrics, bug-reports, retention) also accept an optional ?platform= filter over the rows' OS signal. Valid values: all | mobile | desktop | windows | macos | linux | android | ios | other (mobile = android+ios, desktop = windows+macos+linux). Any other value is a 400 "invalid platform" — never silently ignored. crashes/summary scopes both its crash numerator and its session denominator. The filter never touches live CCU, adoption denominators or the billed monthly CCU peak — your bill never changes with it.
Fleet & log-pull writes (write scope, except fulfill which is ingest): enrich a server's metadata, raise a player-log pull, and let a targeted client honour it (uploading only its own log). The server registry row is created lazily by role=server telemetry, so POST /servers only enriches an existing server (unknown serverId → 404).
/api/v1/serversBody { serverId, region?, capacity?, hostname?, build?, status? }/api/v1/pull-requestsBody { targetType, targetValue, reason, ttlSeconds? } → 201 { requestId }/api/v1/pull-requests/{requestId}/fulfillBody { userId?, sessionId?, matchId?, serverId? } → 201 { logUpload }Triage writes back over the same token auth — set { "status": "open" | "resolved" | "ignored" } on a signature or a single bug report:
/api/v1/signatures/{signature}/statusBody { status, note? }/api/v1/bug-reports/{bugId}/status?at=…Body { status }Funnels & conditions
Funnel steps accept attribute conditions in a canonical syntax shared by the funnel URL, saved funnels, and dashboard widgets: name alone, name{key=value} (equals), or name{key!=value} (not-equals). Multiple conditions are ;-separated and ANDed (max 4 per step); values are compared as strings; != also matches events that don't carry the key. The same key=value;key2!=value2 shape drives the page-wide ?attr= filter, and ?player= scopes the whole analytics page to one userId. In the UI a 3-dropdown picker (key / = ≠ / value) is fed by a persistent per-event attribute catalog, so attributes stay selectable even after their rows age out of the studio's retention window (30 days on the free tier, up to 90 configurable).
Symbols API
Upload debug symbols (PDB / dSYM / .sym) per build from CI so a symbol set is on file for every build, ready for native frames to resolve as that step ships — the tombstack-symbols CLI wraps these calls. Managed (C#) stack traces are already readable without any upload. Registration is idempotent on (game, build, debugId, module) and returns a presigned S3 POST for the file. POST needs the write scope: use a studio key (tmb_st_…) with ?gameId= — per-game tokens are ingest-only and 403 here.
/api/v1/symbols?gameId=…201 { upload: { url, fields } } — presigned S3 POST/api/v1/symbols?buildVersion=2.4.1List registered symbolsMCP — hosted AI access
Tombstack hosts a remote MCP server — nothing to download, build, or run. Enable MCP access (€20/month per studio) on your billing page, mint a key (tmb_mcp_…) scoped to all your games or a single game, and point Claude, Cursor, or any MCP client at the endpoint:
list_games first and passes gameId to each tool; a single-game key needs no gameId at all. Revoking a key or disabling the add-on cuts access instantly.SDKs & repositories
Everything you install ships from public repositories or straight from this site:
Unity SDK — AnkleBreaker-Studio/tombstack-unity (public). Install via UPM git URL https://github.com/AnkleBreaker-Studio/tombstack-unity.git#v0.19.1 — or, for the current release, grab the tarball from the download page.
Native C/C++ SDK — AnkleBreaker-Studio/tombstack-native (public, v0.9.1). C99 DLL ABI for any engine, with offline crash sidecars the CLI uploader drains; minidump capture is coming (Phase 2).
CLI — tombstack-doctor, tombstack-upload and tombstack-symbols, zero-dependency Node 18+, published to the public tombstack-cli mirror — run with npx --package github:AnkleBreaker-Studio/tombstack-cli … (no checkout, no install).