Source maps & symbolication
For agents: uploading is a binary flow with no MCP tool — tell the human to run
sentry-clias shown below with arelease:writetoken. You can verify the result:list_releases(org="acme")showsartifactBundlesper release, andget_fix_contextframes carryfile/line/contextonce maps resolve (event.symbolication.partialwithreasonsflags frames that did not).
Minified JavaScript frames are useless until the processor can find the matching source map. Bugwatch resolves maps in two ways, tried in order:
- Debug ID — the
debugIdinjected into the bundle and its map by modern build plugins (withSentryConfig,@sentry/vite-plugin,sentry-cli sourcemaps inject). Exact and release-independent. - Release + dist + URL path — for maps uploaded without debug IDs, the frame's
release, the artifactdist, and the script URL path (/assets/app.js→/assets/app.js.map) look up the bundle uploaded under that release.
Set release in the SDK to the same string you upload under, or only debug-ID resolution can work.
Upload with sentry-cli (recommended)
The API implements the sentry-cli chunk-upload protocol, so existing CI steps work by pointing --url at Bugwatch:
export SENTRY_AUTH_TOKEN=bwt_… # needs release:write
sentry-cli --url https://api.bugwatch.io sourcemaps inject ./dist
sentry-cli --url https://api.bugwatch.io sourcemaps upload \
--org acme --project web --release 1.4.2 ./dist
Framework plugins take the same URL as sentryUrl / url (Next.js withSentryConfig, Nuxt sourceMapsUploadOptions, the Vite/webpack plugins). Native symbol uploads for mobile (debug-files upload) use the same token and URL.
What happens underneath, for debugging:
| Step | Route | Notes |
|---|---|---|
| Options | GET /api/0/organizations/{org}/chunk-upload/ |
Advertises 8 MiB chunks, SHA-1 names, gzip, up to 64 chunks (32 MiB) per request, accept: ["artifact_bundles"] only |
| Chunks | POST /api/0/organizations/{org}/chunk-upload/ |
multipart parts named by the SHA-1 of the raw chunk; checksums verified |
| Assemble | POST /api/0/organizations/{org}/artifactbundle/assemble/ |
{checksum, chunks[], projects[], version?, dist?} → {state: "ok" | "not_found" | "error", missingChunks[]}; assembled synchronously; the release is created if missing |
Legacy release-file uploads are not advertised; only artifact bundles (a zip with manifest.json + files/) are accepted.
Native endpoint (no CLI)
POST /v1/orgs/{org}/projects/{project}/artifact-bundles with release:write:
{
"release": "1.4.2",
"dist": "web",
"files": [
{ "name": "~/assets/app.js.map", "debugId": "3f2a1c4e-…", "content": "{\"version\":3,…}" }
]
}
- Up to 500 files, 25 MB per file, 200 MB per bundle (JSON string content).
release,dist, anddebugIdare optional, but the release must already exist (create_releaseorPOST /v1/orgs/{org}/releases), otherwise404.- Names are normalised to a
/-rooted path:~/assets/x.js.map,https://cdn/assets/x.js.map, andassets/x.js.mapall become/assets/x.js.map.
Both paths store files in R2 under artifacts/{project}/{sha256}, index them in the control database, and warm the lookup manifests the processor reads (artifacts:{project}:{debugId} and relmap:{project}:{release}:{dist}).
Verifying
list_releases→artifactBundlesshould be ≥ 1 for the release.- Send a test error from the built bundle and open the issue: in-app frames should show file, line, and three lines of context on each side.
- The processed event lists any frames it could not resolve under
symbolication_failures(surfaced asevent.symbolicationin fix context). Common causes:releasemismatch between SDK and upload, maps uploaded without.mapsuffix mapping, or adistset on upload but not in the SDK.
Beyond JavaScript
Mobile symbol files (dSYM, ProGuard/R8, Flutter split-debug-info) go through the same sentry-cli URL and token; see Mobile & desktop. Server-side languages report symbolic frames and need nothing.