Home

BugTape docs

Platforms

Web, iOS, React Native, server — what each sends.

One project, one key, one inbox. Web, iOS, React Native, Android, Flutter and server failures all POST to POST /v1/ingest. The platform field is what lets the console tell them apart. On the v1 web/server path, platform is not part of the fingerprint, so the same failure can span platforms. Native v2 fingerprints hash a canonical JSON blob that includes platform plus application/build/site. Do not change that hash. A native crash does not merge with a web report of the same title.

The vocabulary

Seven values, closed set (packages/api/src/bugs/platform.ts):

ValueLabelTypical source
webWeb@bugtape/sdk in a browser
iosiOSBugTapeCore alpha
androidAndroidyour own POST, or an OkHttp user agent
react-nativeReact Nativedocs/examples/react-native/bugtape.ts
flutterFlutteryour own POST, or a Dart user agent
serverServerdocs/examples/server/evidence-floor.md · curl.md
otherOtheranything else (desktop, Electron, Tauri, unknown)

Common aliases are normalised, case-insensitively, with _/space folded to -: iPhone, ipad, swift, swiftui, uikit, macosios; expo, rn, reactnativereact-native; browser, js, spa, pwaweb; node, bun, deno, python, rails, go, lambda, worker, cron, cliserver; dartflutter; electron, tauri, desktop, unknownother. An unrecognised string becomes other rather than being rejected.

How the platform is decided

Three steps, first hit wins:

  1. The explicit platform field on the ingest payload. Always send it if you can.
  2. metadata.platform — the legacy location, still honoured.
  3. The user agent, in this order:

| User agent contains | Platform | |---------------------|----------| | Mozilla/ | web | | CFNetwork, Darwin/, iOS, iPhone, iPad | ios | | Dalvik, okhttp, Android | android | | Flutter, Dart/ | flutter | | node, undici, axios, curl, python, Go-http-client, Java/, Ruby, PHP, Wget, Deno, Bun/ | server | | (no user agent at all) | server | | anything else | other |

A server-side hook that sends no user agent is therefore server by default, which is almost always what you want.

What each platform captures

WebiOSReact NativeServer
Uncaught errorsyesno fatal native captureJS errors only, via ErrorUtilsyou POST them
Unhandled rejectionsyesn/ayes, via Hermes trackern/a
Handled errorscaptureclient.capture(error, screen:fields:)BugTape.capture(err, { screen })one POST each
Breadcrumbs / consolefull console capturelast 100 by default, explicit hookslast 100, manualput them in events
DOM replayyes (rrweb)nonono
Network waterfallyes (fetch, XHR, resource timing)nonono
Rage clicksyesnonono
Device / OSbrowser + OS parsed from UAutsname model + OS versionPlatform.OS/Version/Modelwhatever you send
Release / useryesyesyesyes

The legacy mobile examples are deliberately thin: no replay, no network interception, no symbolication, no native module. The iOS drop-in captures handled errors and manual messages only. Fatal native crash capture is unavailable pending a qualified native SDK; it installs no signal or uncaught-exception handlers. React Native JS error capture does not capture fatal native crashes. See each example's README for details.

The current Swift core package adds consent controls, a durable bounded offline queue and managed installation delivery receipts. Swift on macOS sends platform: other and osFamily: macos; iOS sends ios/ios. Neither provides native replay or fatal recording.

Field mapping

Ingest fieldWebNon-webShows up as
platformwebios / react-native / server / …Platform facet + row tags
urlthe real page URLthe screen name: app://Checkout, worker://billing/chargeFingerprint input, and the "where" on the occurrence
titleTypeError: …must be shaped <Token>Error: <message>Issue title, and the error-kind fingerprint input
releasebuild sha / deploy idapp version or build (1.4.0 (218))Regression detection, "last release" per user
environmentproduction / stagingsameTriage filter
userIdidentify()identify()Affected users, user timeline
reporterEmailreporteridentify(email:)User attribution when there is no userId
sessionIdbrowser sessionone UUID per app launchOccurrence grouping
deviceparsed from UAiPhone15,3, Pixel 8Occurrence metadata + Impact breakdown
osVersionparsed from UA17.4.1Occurrence metadata + Impact breakdown
metadataanythingbundle id, locale, your extraOccurrence detail
eventsDOM, network, console, errorsbreadcrumbs + one error eventTimeline on the bug detail

Fingerprint by path

Web, server, and other use v1:

SHA256(errorType + ":" + normalizedMessage + ":" + urlPattern).slice(0, 16)

errorType is read off a ^(\w+Error): title prefix. urlPattern comes from url. A client that sends title: "crash" and no url gives every v1 failure the same fingerprint. Shape titles <Token>Error: <message> and send a meaningful url (app://<ScreenName>, worker://<service>/<path>).

Native (ios / android / react-native / flutter) uses v2. The fingerprint is SHA256 of a canonical JSON failureSignature that includes version, platform, applicationId, buildId, signal, and the symbol site or image offset. Title and url are not that hash. Without application/build and a symbol site or image offset, the capture isolates on its own id. Do not send a native crash expecting it to merge with a web report of the same title. Do not change the native v2 hash.

Server and React Native examples still shape title and url for the v1 path. Pass screen: on every web-style capture call.

How the console shows it

  • Issues — a Platform select filters the list to one platform. Each row carries a small text tag per platform seen; an issue hit on web and iOS shows both.
  • Bug detail — a Platform fact row lists every platform for that issue. The Impact tab breaks occurrences down by platform, browser and OS.
  • Affected users (bug detail) — one row per end user, with a Platforms column and their last release.
  • End user page / user timeline — every occurrence one person hit across every bug, with a Platform column per row.
  • Setup — the snippet tabs (npm, Script tag, Next.js, iOS, React Native, Server / curl) each carry a platform, and Send console sample (POST /v1/projects/:id/test-event) mints a real occurrence tagged with the tab you picked, through the normal pipeline, before you install anything. Console samples and aggregate report counts do not verify client installation.

How agents see it

Over MCP (see agents.md):

  • list_bug_groups { platform } — grouped triage, filtered to groups with an occurrence on that platform. Each group returns a platforms array.
  • get_bug_users { bugId } — one row per affected end user, with platforms, lastRelease and lastOccurrenceId.
  • get_user_timeline { userKey } — everything one user hit across bugs and projects, with platform, release and environment on each occurrence.

Use the platform filter to find groups with iOS reports. Groups may also contain other platforms; this filter does not mean "iOS-only" or prove fatal crash capture.

Design rule

v1 web/server: platform is not in the fingerprint. The same failure from web and a worker stays one issue with two platforms.

Native v2: platform is in the fingerprint. That is the live hash. Do not change it to match the v1 sentence. Filter by platform when you want to; do not expect a native crash and a web report of the same title to be one issue.

Where to go next