4 · BugWatch — Source Maps & Symbols

POST/api/v1/bugwatch/artifacts/presign

Artifact upload · step 1 – presign

Step 1 of 2 — request a presigned URL for a text symbolication artifact.

Scope: symbols:upload · Auth: project secret key (keyId:secret)

artifactTypeFilePlatform
r8 / proguardmapping.txtandroid
sourcemap.mapios · react-native · android
dart-symbols--split-debug-info outputflutter
dsymApple dSYM referenced as an artifactios

The file never passes through this API. You presign, PUT the bytes straight to storage, then confirm. That is why uploads are not bound by request-size limits — the previous raw-body routes were rejected by the edge proxy for any real symbol file.

1. POST  …/presign                       → { uploadId, uploadUrl, expiresAt }
2. PUT   <uploadUrl>                      → the raw bytes, streamed to storage
3. POST  …/uploads/{uploadId}/complete    → verified, then queued

The API key goes on steps 1 and 3 only — never to the storage host, which is authorised by the presigned URL's own signature.

Uploads are idempotent per (release, platform, artifactType) — completing a new upload replaces the previous artifact and deletes the superseded file.

The release you declare must match the release your SDK reports at runtime, or the artifact exists but nothing matches it and stacks stay unreadable.

Most integrators should use the CLI, which performs all three steps, streams the file (flat memory regardless of size) and retries transient failures:

# Binary debug-symbol archives — Apple dSYM, Android native .so
npx @newinstance/bugwatch-cli symbols upload MyApp.xcarchive \
  --release "1.4.2" --build-number "318"

# Text artifacts — R8/ProGuard mapping, source map, Dart symbols
npx @newinstance/bugwatch-cli artifacts upload mapping.txt \
  --release "1.4.2" --platform android --type r8

The two commands are not interchangeable: symbols upload accepts binary archives only and validates magic bytes, so a mapping.txt or .map is rejected.

Headers

x-api-key

Responses

Successful response

null
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/artifacts/presign

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/artifacts/presign'
POST/api/v1/bugwatch/artifacts/uploads/complete

Artifact upload · step 2 – complete

Step 2 of 2 — confirm the artifact landed and swap it in.

Scope: symbols:upload

What is verified

  • complete confirms the object actually landed in storage and that its size matches what was declared at presign.
  • The pending row is looked up scoped to the calling project, so one project can never finalise another's upload (a miss returns 404).
  • Only an AWAITING_UPLOAD row can be completed, so a completion cannot be replayed to re-queue processing.
  • For debug symbols the worker re-hashes the real bytes before indexing and rejects a mismatch (CHECKSUM_MISMATCH). A declared checksum is never trusted on its own.

Responses

  • 201 { fileId, sha256 } — stored and now the active artifact for that (release, platform, artifactType).
  • 409 — the object was never PUT to uploadUrl.
  • 400 — the stored size does not match what was declared at presign.
  • 404 — no such upload for this project.

Headers

x-api-key

Responses

Successful response

null
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/artifacts/uploads/complete

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/artifacts/uploads/complete'
POST/api/v1/bugwatch/debug-symbols/presign

Debug symbols · step 1 – presign

Step 1 of 2 — request a presigned URL for a binary debug-symbol archive.

Scope: symbols:upload · Auth: project secret key (keyId:secret)

PlatformAccepted
ios · macos · tvos · watchos · visionos · catalyst.zip of .dSYM bundles, .xcarchive dSYMs, or a raw Mach-O
androidRaw ELF .so / .debug

The file never passes through this API. You presign, PUT the bytes straight to storage, then confirm. That is why uploads are not bound by request-size limits — the previous raw-body routes were rejected by the edge proxy for any real symbol file.

1. POST  …/presign                       → { uploadId, uploadUrl, expiresAt }
2. PUT   <uploadUrl>                      → the raw bytes, streamed to storage
3. POST  …/uploads/{uploadId}/complete    → verified, then queued

The API key goes on steps 1 and 3 only — never to the storage host, which is authorised by the presigned URL's own signature.

Matching is by debug UUID / build-id, not by release name — the identifier embedded in the binary. That is why an upload from any build machine resolves crashes from any device running that exact binary, and why release here is metadata for search rather than the match key. (Source maps and mappings are the opposite: those match on release + platform.)

Declaring a sha256 that already exists for this project short-circuits as a duplicate and returns no uploadUrl — the transfer is skipped entirely.

Most integrators should use the CLI, which performs all three steps, streams the file (flat memory regardless of size) and retries transient failures:

# Binary debug-symbol archives — Apple dSYM, Android native .so
npx @newinstance/bugwatch-cli symbols upload MyApp.xcarchive \
  --release "1.4.2" --build-number "318"

# Text artifacts — R8/ProGuard mapping, source map, Dart symbols
npx @newinstance/bugwatch-cli artifacts upload mapping.txt \
  --release "1.4.2" --platform android --type r8

The two commands are not interchangeable: symbols upload accepts binary archives only and validates magic bytes, so a mapping.txt or .map is rejected.

Headers

x-api-key

Responses

Successful response

null
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/debug-symbols/presign

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/debug-symbols/presign'
POST/api/v1/bugwatch/debug-symbols/uploads/complete

Debug symbols · step 2 – complete

Step 2 of 2 — confirm the archive landed and queue it for indexing.

Scope: symbols:upload

What is verified

  • complete confirms the object actually landed in storage and that its size matches what was declared at presign.
  • The pending row is looked up scoped to the calling project, so one project can never finalise another's upload (a miss returns 404).
  • Only an AWAITING_UPLOAD row can be completed, so a completion cannot be replayed to re-queue processing.
  • For debug symbols the worker re-hashes the real bytes before indexing and rejects a mismatch (CHECKSUM_MISMATCH). A declared checksum is never trusted on its own.

Archive-type validation happens in the worker, not here, because this API never sees the bytes. An archive that is not a zip / Mach-O / ELF lands as INVALID with UNRECOGNIZED_ARCHIVE; an ELF declared under an Apple platform fails with ARCHIVE_PLATFORM_MISMATCH.

Responses

  • 200 { uploadId, status: "QUEUED" } — accepted; poll Get upload status for indexing progress.
  • 409 — the archive was never PUT to uploadUrl.
  • 400 — declared size does not match the stored object.

Headers

x-api-key

Responses

Successful response

null
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/complete

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/complete'
GET/api/v1/bugwatch/debug-symbols/uploads

List debug symbol uploads

List recent debug symbol uploads for this project, newest first.

Scope: symbols:read

Query params

  • limit (optional, int 1–200, default 50)
  • status (optional, string) — filter by QUEUED | PROCESSING | DONE | FAILED

Success — 200 OK returns { uploads: [...] }

Headers

x-api-key

Parameters

limitquerystringdefault: Number of results (default 50, max 200)

Responses

200 – Uploads list

{
  "uploads": [
    {
      "uploadId": "507f1f77bcf86cd799439011",
      "status": "DONE",
      "platform": "ios",
      "release": "2.1.0",
      "buildNumber": "1042",
      "originalFilename": "MyApp.app.dSYM.zip",
      "uploadedSize": 4823012,
      "discoveredUuids": 3,
      "validObjects": 3,
      "invalidObjects": 0,
      "createdAt": "2026-06-26T10:00:00.000Z",
      "completedAt": "2026-06-26T10:00:15.000Z"
    }
  ]
}
boltTry it
env
GEThttp://localhost:5050/api/v1/bugwatch/debug-symbols/uploads

Query parameters

limit

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/v1/bugwatch/debug-symbols/uploads'
POST/api/v1/bugwatch/debug-symbols/uploads/reprocess

Reprocess – re-symbolicate waiting crashes

Trigger re-symbolication for crashes that arrived before symbols were uploaded.

Scope: symbols:reprocess

Previously-unsymbolicated crashes are then re-processed with the newly-uploaded symbols.

Body: empty

Success — 200 OK

{ "requeued": 12 }

Headers

x-api-key

Responses

200 – Re-queued

{
  "requeued": 12
}
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/reprocess

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/reprocess'