Developer documentation

SHIP THE SDK. READ THE GRAVES.

Everything to wire Tombstack into your engine and pull data back out — with copy-paste samples for every call.

Quickstart API reference

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.

setup
# 1. Sign up — creates your studio (you become owner)
# 2. On Games -> New game (name + engine), then open it
# 3. SDK tokens -> Mint token (shown once - store as a build secret)
Bootstrap.csC# · Unity
using AnkleBreaker.Tombstack;
 
Tombstack.Init("tmb_live_9f4c…a21e", "https://your-tombstack-host");
Tombstack.SetUser("user-123", steamId: "7656119…"); // when auth resolves
Tombstack.SetConsent(true); // GDPR / store-policy gate
That's it. Trigger a test exception and watch it land on the game dashboard with its signature, rate, and 30-day trend within seconds.

Core concepts

Four primitives power everything in Tombstack:

GameOne title in a studio. The per-game SDK token scopes all ingestion to it.
SignatureCrashes grouped by fingerprint, so 40,000 crashes become one grave you can triage.
Build versionEvery crash, heartbeat and event is tagged with one — spot regressions instantly.
BreadcrumbsThe most recent 50 log lines before a crash, shown as a timeline on the signature page.

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…:

Tarball (recommended)
https://d37yvxlv29ed7d.cloudfront.net/downloads/com.anklebreaker.tombstack-0.9.0.tgz

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.9.0. 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:

Exception captureUnhandled exceptions on every thread, unobserved Task exceptions and AppDomain exceptions are reported automatically — deduped per signature (≤1 report/min; repeats become a breadcrumb counter).
Session logEvery log line mirrors into a rolling ~512 KB session.log under persistentDataPath/Tombstack, uploaded with every crash and every bug report via a presigned PUT.
Unclean shutdownsInit writes a session marker; a clean quit removes it. If it survives to the next launch — hard crash, OOM kill, force quit — the SDK reports a synthetic unclean-shutdown crash and uploads the previous session's log. Clean quits report nothing.

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.

C# · Unity
try {
inventory.Equip(item);
} catch (Exception e) {
Tombstack.Capture(e); // symbolicated + grouped by signature
}

Player bug reports

Let players file bug reports from inside the game — optionally with a screenshot. They land in the dashboard alongside crashes.

C# · Unity
Tombstack.ReportBug("Quest log empty after load", "ui");
API

Authentication

All endpoints authenticate with a token sent as Authorization: Bearer … (or x-api-key). Two kinds exist:

tmb_… (per-game)Minted per game in the dashboard (or the editor hub). Resolves to exactly one game/studio scope — the SDK token your builds ship with. Ingest-only by default: it can POST to /ingest/* but cannot read crashes/PII or write triage unless explicitly granted read/write scope.
tmb_st_… (studio)Admin-minted, never shipped. Implicitly satisfies every scope (ingest/read/write) and is what the read + triage API and MCP / CI fan-out use. Works on every game-scoped endpoint by adding ?gameId=<id> (the game must belong to the key's studio).

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.

GET/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): 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.

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.

API

Ingest API

Game clients are treated as hostile input: every field is validated, clamped and rate-limited. os ∈ {windows,macos,linux,other}, arch ∈ {x64,arm64,x86,other}, timestamps within the 90-day window.

POST/api/v1/ingest/crashes201 { crashId, logUpload? }
POST/api/v1/ingest/heartbeats202 { accepted }
POST/api/v1/ingest/bug-reports201 { bugId, logUpload? }
POST/api/v1/ingest/events202 { eventId }
POST/api/v1/ingest/metrics202 { metricId } — single numeric metric
POST/api/v1/ingest/events:batch202 { accepted, dropped, skewMs }
POST/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.

metrics:batch
curl -X POST https://your-tombstack-host/api/v1/ingest/metrics:batch \
-H "Authorization: Bearer tmb_live_9f4c…a21e" \
-H "Content-Type: application/json" \
-d '{
"sentAtIso": "2026-06-11T12:00:05Z",
"items": [
{ "name": "fps", "value": 59.8, "unit": "fps",
"occurredAtIso": "2026-06-11T12:00:00Z",
"buildVersion": "2.4.1", "os": "windows", "arch": "x64",
"role": "client", "matchId": "m-1", "sessionId": "sess-9" },
{ "name": "rtt_ms", "value": 42, "unit": "ms",
"occurredAtIso": "2026-06-11T12:00:01Z",
"buildVersion": "2.4.1", "os": "windows", "arch": "x64",
"role": "client", "matchId": "m-1" }
]
}'

Binary artifacts upload out-of-band via presigned S3 PUTs (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.

request
curl -X POST https://your-tombstack-host/api/v1/ingest/crashes \
-H "Authorization: Bearer tmb_live_9f4c…a21e" \
-H "Content-Type: application/json" \
-d '{
"occurredAtIso": "2026-06-08T11:40:00Z",
"buildVersion": "2.4.1",
"os": "windows",
"arch": "x64",
"signature": "SIGSEGV@InventoryService.Equip",
"stackHint": "NullReference in InventoryService.Equip"
}'
201 Created
{
"success": true,
"data": { "crashId": "01J…", "minidumpUpload": null, "logUpload": null }
}
API

Read API

Pull aggregated and recent data back out — for dashboards, automations or your own tooling.

GET/api/v1/read/crashes/summary?days=30Aggregated
GET/api/v1/read/crashes?days=7Recent rows
GET/api/v1/read/signatures?days=30All signatures + triage status
GET/api/v1/read/signatures/{signature}Drill-down + trend + logUrl
GET/api/v1/read/bug-reports?days=30Recent reports
GET/api/v1/read/bug-reports/{bugId}?at=…One report + screenshot/logUrl
GET/api/v1/read/events?days=7Recent events/logs
GET/api/v1/read/players/{userId}/crashesOne player's crashes
GET/api/v1/read/usageCCU peak, plan, est. USD
GET/api/v1/read/matches?days=7Derived matches — span, players, server, crash count
GET/api/v1/read/matches/{matchId}One match's full telemetry timeline
GET/api/v1/read/metrics?name=fps&days=7Series + percentiles (groupBy serverId|matchId|buildVersion|os)
GET/api/v1/read/retention?days=30DAU/WAU/MAU + D1/D7/D30 cohort
GET/api/v1/read/servers?days=7Fleet list — live CCU, crash-free, last seen
GET/api/v1/read/servers/{serverId}Server detail — metadata, health, recent crashes
GET/api/v1/read/servers/{serverId}/connected?days=1Players connected to the server in the window
GET/api/v1/read/pull-requestsLog-pull status + audit trail

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

POST/api/v1/serversBody { serverId, region?, capacity?, hostname?, build?, status? }
POST/api/v1/pull-requestsBody { targetType, targetValue, reason, ttlSeconds? } → 201 { requestId }
POST/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:

POST/api/v1/signatures/{signature}/statusBody { status, note? }
POST/api/v1/bug-reports/{bugId}/status?at=…Body { status }
200 OK
{
"success": true,
"data": {
"totalCrashes24h": 312,
"crashSpike": false,
"topSignatures": [
{ "signature": "SIGSEGV@InventoryService.Equip", "count": 188, "affectedUsers": 142 }
]
}
}
API

Symbols API

Upload debug symbols (PDB / dSYM / .sym) per build from CI so native crashes can be symbolicated — the tombstack-symbols CLI wraps these calls. Registration is idempotent on (game, build, debugId, module) and returns a presigned S3 PUT for the file itself.

POST/api/v1/symbols201 { upload: { url, key } } — presigned PUT
GET/api/v1/symbols?buildVersion=2.4.1List registered symbols
request
curl -X POST https://your-tombstack-host/api/v1/symbols \
-H "Authorization: Bearer tmb_live_9f4c…a21e" \
-H "Content-Type: application/json" \
-d '{
"buildVersion": "2.4.1",
"moduleName": "Game.dll",
"debugId": "3C8DA0F8-…-1",
"os": "windows",
"size": 18874368
}'
API

MCP setup

Point Claude or Cursor at the Tombstack MCP server (@anklebreaker/tombstack-mcp) to triage crashes with an AI — it wraps the read API as MCP tools.

mcp config
{
"mcpServers": {
"tombstack": {
"command": "npx",
"args": ["-y", "@anklebreaker/tombstack-mcp"],
"env": {
"TOMBSTACK_BASE_URL": "https://your-tombstack-host",
"TOMBSTACK_TOKEN": "tmb_live_9f4c…a21e"
}
}
}
}
Tools include crash summary, list crashes, signature detail, player crashes, bug reports, events, usage, and set-status — so an AI can both read and triage.

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.9.0 or grab the tarball from the download page.

Native C/C++ SDK AnkleBreaker-Studio/tombstack-native (public, v0.1.0, in development). C99 DLL ABI for any engine, with offline crash sidecars the CLI uploader drains; minidump capture is coming (Phase 2).

CLItombstack-doctor, tombstack-upload and tombstack-symbols, zero-dependency Node 18+ — download the zip.

Ready to wire it in?
Grab a token and ship your first event in minutes.
Get an SDK token