New Instance Platform

v1.0.0

New Instance Platform — REST API

Use this collection for the supported New Instance REST workflows and product integration guides.

Platform products covered:

  • BugWatch — error/log ingest, OpenTelemetry, Prometheus, Jaeger, RUM, debug symbols
  • Support Tickets — sign-in links, ticket CRUD, comments
  • Secret Manager — encrypted MEK + variable fetch
  • Chat — embeddable widget integration
  • Docs Portal — dashboard-managed docs publishing

Import instructionslink

  1. Import this collection into your API client or documentation tool
  2. Import the matching environment file (NewInstance.local, NewInstance.staging, or NewInstance.production)
  3. Select the environment from the dropdown (top-right)
  4. Set apiKey, projectId, mobileAppSecret, appId, environment, release

API base URLslink

  • Local dev: http://localhost:5050
  • Production: https://api.newinstance.cloud

Authenticationlink

All requests use x-api-key: {{apiKey}} except:

  • BugWatch browser ingest → x-bugwatch-session: {{sessionToken}}
  • BugWatch mobile ingest → x-bugwatch-token: {{mobileToken}} (auto-built by pre-request script)

0 · Getting Started

1 endpoint
GET/api/auth/verify

Verify API key

Verify that your API key is valid and inspect its metadata.

Run this first after generating a key to confirm it is active and check the environment (test vs live).

Authentication

  • Header: x-api-key: {{apiKey}}

Success — 200 OK

{
  "success": true,
  "message": "API key is valid",
  "business": { "id": "org_abc123", "name": "Acme Corp", "slug": "acme-corp" },
  "key": { "id": "sk_test_abc123", "environment": "test" },
  "timestamp": "2026-06-26T10:00:00.000Z"
}

Common errors

  • 401 — key missing, malformed, or revoked
  • 403 — key found but inactive

Headers

x-api-key

Responses

200 – Key valid

{
  "success": true,
  "message": "API key is valid",
  "business": {
    "id": "org_abc123",
    "name": "Acme Corp",
    "slug": "acme-corp"
  },
  "key": {
    "id": "sk_test_abc123",
    "environment": "test"
  },
  "timestamp": "2026-06-26T10:00:00.000Z"
}
boltTry it
env
GEThttp://localhost:5050/api/auth/verify

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/auth/verify'

1 · BugWatch — Ingest & Errors

2 endpoints
POST/api/v1/bugwatch/browser-session

Mint browser session token

Mint a short-lived browser session token. Call this from your backend using the secret key — the token is then passed to your frontend. The secret never reaches the client.

Scope: ingest:write

Request

  • Method: POST
  • Header: x-api-key: {{apiKey}}
  • Body: empty

Success — 200 OK

{ "token": "<session-token>", "expiresAt": "2026-06-26T12:15:00.000Z" }

Common errors

  • 401 — invalid/missing x-api-key
  • 403 — key lacks ingest:write scope

Headers

x-api-key

Responses

200 – Token minted

{
  "token": "eyJhbGciOiJIUzI1NiJ9.example",
  "expiresAt": "2026-06-26T12:15:00.000Z"
}
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/browser-session

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/browser-session'
POST/api/v1/bugwatch/ingest

Ingest – log event (JSON)

Ingest logs and error events from server-side or CI environments.

Scope: ingest:write

Request

  • Header: x-api-key: {{apiKey}}
  • Content-Type: application/json (single object or array) or application/x-ndjson (one event per line)
  • Max body: 2 MB

Event fields

FieldTypeNotes
levelnumber10/20/30/40/50/60
timenumberUnix ms timestamp
messagestringHuman-readable text
releasestringApp build version
environmentstringproduction / staging / development
eventIdstringOptional; enables deduplication within 10-min window
tagsobjectFlat key-value labels (≤50 keys)
userobject{id, email, username, ip} — only these four keys; others are silently dropped
traceIdstringHex distributed trace id (≤32 chars)
spanIdstringSpan id
exceptionobject{type, value, stacktrace: {frames}}
breadcrumbsarrayLeading events

Success — 202 Accepted

{ "ingested": 1, "skipped": 0, "deduped": 0 }

Headers

x-api-key

Request body

application/json
{
  "level": 30,
  "time": {{nowMs}},
  "message": "User signed in",
  "release": "1.0.0",
  "environment": "production",
  "tags": { "region": "eu-west-1" },
  "user": { "id": "usr_123", "email": "alice@example.com" }
}

Responses

Successful response

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

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/ingest' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "level": 30,
  "time": {{nowMs}},
  "message": "User signed in",
  "release": "1.0.0",
  "environment": "production",
  "tags": { "region": "eu-west-1" },
  "user": { "id": "usr_123", "email": "alice@example.com" }
}'

3 · BugWatch — Browser & Mobile

2 endpoints
POST/api/v1/bugwatch/ingest/browser

Browser ingest – error event

Ingest events from browser / SPA applications.

Authentication: x-bugwatch-session — a short-lived token minted by your backend via POST /api/v1/bugwatch/browser-session. Run "Mint browser session token" first; the test script saves it as {{sessionToken}} automatically.

The secret key never reaches the browser. The session token is ingest-only, scoped to one project+environment, and honours the project's allowedOrigins allow-list.

Request

  • Header: x-bugwatch-session: {{sessionToken}}
  • Content-Type: application/json or application/x-ndjson
  • Max body: 2 MB
  • CORS: allowed from any origin in the project's allowedOrigins (empty = allow all)

Success — 202 Accepted

{ "ingested": 1, "skipped": 0, "deduped": 0 }

Common errors

  • 401 — invalid or expired session token
  • 403 — origin not in allowedOrigins (origin_not_allowed)
  • 404 — project not found or inactive

Headers

x-bugwatch-session

Request body

application/json
{
  "level": 50,
  "time": {{nowMs}},
  "message": "Uncaught TypeError in browser",
  "release": "1.0.0",
  "environment": "production",
  "tags": { "browser": "Chrome", "page": "/checkout" },
  "user": { "id": "usr_browser_001", "ip": "203.0.113.42" },
  "exception": {
    "type": "TypeError",
    "value": "Cannot read properties of null (reading 'getAttribute')",
    "stacktrace": {
      "frames": [
        { "filename": "https://app.example.com/static/js/main.chunk.js", "function": "handleClick", "lineno": 112, "colno": 45 }
      ]
    }
  }
}

Responses

202 – Browser event ingested

{
  "ingested": 1,
  "skipped": 0,
  "deduped": 0
}
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/ingest/browser

Headers

x-bugwatch-session

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/ingest/browser' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "level": 50,
  "time": {{nowMs}},
  "message": "Uncaught TypeError in browser",
  "release": "1.0.0",
  "environment": "production",
  "tags": { "browser": "Chrome", "page": "/checkout" },
  "user": { "id": "usr_browser_001", "ip": "203.0.113.42" },
  "exception": {
    "type": "TypeError",
    "value": "Cannot read properties of null (reading '\''getAttribute'\'')",
    "stacktrace": {
      "frames": [
        { "filename": "https://app.example.com/static/js/main.chunk.js", "function": "handleClick", "lineno": 112, "colno": 45 }
      ]
    }
  }
}'
POST/api/v1/bugwatch/ingest/mobile

Mobile ingest – iOS native crash

Ingest events from native mobile apps (iOS, Android, Flutter, React Native).

Authentication: x-bugwatch-token — a client-signed token built on device by the SDK. The app embeds a per-project mobileAppSecret (HKDF-derived), signs each request locally, and sends only the signed token. The secret is never transmitted.

The pre-request script on this request reproduces the exact token the SDK builds using CryptoJS. Set projectId, environment, and mobileAppSecret in your environment before running.

Token format: base64url(claims).base64url(HMAC-SHA256(claims, appSecret))

Claims (deterministic key order, no spaces):

{"pid":"<projectId>","env":"<env>","iat":<nowSec>,"exp":<nowSec+300>,"nonce":"<16 hex chars>"}

Request

  • Header: x-bugwatch-token: {{mobileToken}} (auto-built by pre-request script)
  • Content-Type: application/json or application/x-ndjson
  • Max body: 2 MB
  • Rate limit: 12,000 events / 60 seconds per project

Success — 202 Accepted

{ "ingested": 1, "skipped": 0, "deduped": 0 }

Common errors

  • 401 — invalid/expired/malformed token
  • 429 — rate limited

Event model (what each SDK puts on the wire)

One merged event per request (eventId, time, level required). The four native SDKs share this endpoint:

  • Native crashes (iOS, Android NDK) carry payloadVersion: 2 with top-level binaryImages + nativeStacktrace (iOS) / nativeFrames (Android) — raw instruction_addr values the worker symbolicates against the uploaded dSYM / ELF by UUID / build-id.
  • JVM / Dart / JS exceptions (Android, Flutter, React Native) and handled errors carry exception.stacktrace as an array of { filename, function, lineno, colno, in_app } frames — de-obfuscated server-side via the uploaded R8 mapping / dart-symbols / source map.
  • All SDKs also emit ANR / app-hang, message, and release-health session events through this same endpoint.

See the per-platform examples in this folder — iOS native crash, Android JVM, Android NDK, Flutter, React Native — and the matching upload in 4 · Source Maps & Symbols.

Headers

x-bugwatch-token

Request body

application/json
{
  "eventId": "bw_e_{{$guid}}",
  "time": {{nowMs}},
  "level": 60,
  "message": "Fatal: EXC_BAD_ACCESS (SIGSEGV)",
  "platform": "ios",
  "release": "1.0.0",
  "environment": "production",
  "sdk": {
    "name": "bugwatch-ios",
    "version": "0.1.1"
  },
  "device": {
    "model": "iPhone15,2",
    "family": "iPhone",
    "osName": "iOS",
    "osVersion": "17.4.1",
    "bundleId": "com.example.MyApp",
    "appVersion": "1.4.2",
    "appBuild": "318"
  },
  "exception": {
    "type": "EXC_BAD_ACCESS",
    "value": "Attempted to dereference a null pointer",
    "stacktrace": null
  },
  "binaryImages": [
    {
      "name": "MyApp",
      "debug_id": "550e8400-e29b-41d4-a716-446655440001",
      "arch": "arm64",
      "image_addr": "0x100008000",
      "image_size": 65536,
      "is_main_image": true
    },
    {
      "name": "Foundation",
      "debug_id": "7c8f1a22-9b33-4d55-8e21-0a1b2c3d4e5f",
      "arch": "arm64",
      "image_addr": "0x1db000000",
      "image_size": 2097152
    }
  ],
  "nativeStacktrace": [
    {
      "frame_index": 0,
      "instruction_addr": "0x100008234",
      "image_addr": "0x100008000",
      "image_name": "MyApp",
      "in_app": true
    },
    {
      "frame_index": 1,
      "instruction_addr": "0x1db123456",
      "image_addr": "0x1db000000",
      "image_name": "Foundation"
    }
  ],
  "crashedThreadId": 0,
  "payloadVersion": 2,
  "tags": {
    "device": "iPhone 15 Pro"
  },
  "user": {
    "id": "usr_ios_001"
  }
}

Responses

202 – Mobile event ingested

{
  "ingested": 1,
  "skipped": 0,
  "deduped": 0
}
boltTry it
env
POSThttp://localhost:5050/api/v1/bugwatch/ingest/mobile

Headers

x-bugwatch-token

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/bugwatch/ingest/mobile' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "eventId": "bw_e_{{$guid}}",
  "time": {{nowMs}},
  "level": 60,
  "message": "Fatal: EXC_BAD_ACCESS (SIGSEGV)",
  "platform": "ios",
  "release": "1.0.0",
  "environment": "production",
  "sdk": {
    "name": "bugwatch-ios",
    "version": "0.1.1"
  },
  "device": {
    "model": "iPhone15,2",
    "family": "iPhone",
    "osName": "iOS",
    "osVersion": "17.4.1",
    "bundleId": "com.example.MyApp",
    "appVersion": "1.4.2",
    "appBuild": "318"
  },
  "exception": {
    "type": "EXC_BAD_ACCESS",
    "value": "Attempted to dereference a null pointer",
    "stacktrace": null
  },
  "binaryImages": [
    {
      "name": "MyApp",
      "debug_id": "550e8400-e29b-41d4-a716-446655440001",
      "arch": "arm64",
      "image_addr": "0x100008000",
      "image_size": 65536,
      "is_main_image": true
    },
    {
      "name": "Foundation",
      "debug_id": "7c8f1a22-9b33-4d55-8e21-0a1b2c3d4e5f",
      "arch": "arm64",
      "image_addr": "0x1db000000",
      "image_size": 2097152
    }
  ],
  "nativeStacktrace": [
    {
      "frame_index": 0,
      "instruction_addr": "0x100008234",
      "image_addr": "0x100008000",
      "image_name": "MyApp",
      "in_app": true
    },
    {
      "frame_index": 1,
      "instruction_addr": "0x1db123456",
      "image_addr": "0x1db000000",
      "image_name": "Foundation"
    }
  ],
  "crashedThreadId": 0,
  "payloadVersion": 2,
  "tags": {
    "device": "iPhone 15 Pro"
  },
  "user": {
    "id": "usr_ios_001"
  }
}'

4 · BugWatch — Source Maps & Symbols

6 endpoints
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'

5 · BugWatch — OpenTelemetry, Prometheus & Tracing

10 endpoints
POST/v1/logs

OTLP/HTTP – ingest logs (JSON)

Send OpenTelemetry logs to BugWatch via OTLP/HTTP.

Scope: ingest:write

Content-Type options

  • application/json — JSON body (used here)
  • application/x-protobuf — protobuf ExportLogsServiceRequest (used by the OTel Collector)

Request body (JSON form)

{
  "resourceLogs": [{
    "resource": { "attributes": [{ "key": "service.name", "value": { "stringValue": "my-service" } }] },
    "scopeLogs": [{
      "logRecords": [{
        "timeUnixNano": "1750924800000000000",
        "severityNumber": 9,
        "severityText": "INFO",
        "body": { "stringValue": "User signed in" },
        "attributes": [{ "key": "user.id", "value": { "stringValue": "usr_123" } }]
      }]
    }]
  }]
}

Success — 200 OK (empty body on full success)

{}

On partial rejection:

{ "partialSuccess": { "rejectedLogRecords": 2, "errorMessage": "some records rejected" } }

Headers

x-api-key

Request body

application/json
{
  "resourceLogs": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeLogs": [
        {
          "logRecords": [
            {
              "timeUnixNano": "{{nowNano}}",
              "severityNumber": 9,
              "severityText": "INFO",
              "body": {
                "stringValue": "User signed in"
              },
              "attributes": [
                {
                  "key": "user.id",
                  "value": {
                    "stringValue": "usr_123"
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Responses

200 – All accepted

null
boltTry it
env
POSThttp://localhost:5050/v1/logs

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/v1/logs' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "resourceLogs": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeLogs": [
        {
          "logRecords": [
            {
              "timeUnixNano": "{{nowNano}}",
              "severityNumber": 9,
              "severityText": "INFO",
              "body": {
                "stringValue": "User signed in"
              },
              "attributes": [
                {
                  "key": "user.id",
                  "value": {
                    "stringValue": "usr_123"
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}'
POST/v1/traces

OTLP/HTTP – ingest traces (JSON)

Send OpenTelemetry traces to BugWatch via OTLP/HTTP.

Scope: ingest:write

Content-Type options

  • application/json — JSON body (used here)
  • application/x-protobuf — protobuf ExportTraceServiceRequest (used by the OTel Collector)

Request body (JSON form)

{
  "resourceSpans": [{
    "resource": { "attributes": [{ "key": "service.name", "value": { "stringValue": "my-service" } }] },
    "scopeSpans": [{
      "spans": [{
        "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
        "spanId": "00f067aa0ba902b7",
        "name": "HTTP GET /api/users",
        "kind": 2,
        "startTimeUnixNano": "1750924800000000000",
        "endTimeUnixNano": "1750924800050000000",
        "status": { "code": 1 }
      }]
    }]
  }]
}

Success — 200 OK (empty body on full success; partialSuccess on rejection)

Headers

x-api-key

Request body

application/json
{
  "resourceSpans": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeSpans": [
        {
          "spans": [
            {
              "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
              "spanId": "00f067aa0ba902b7",
              "name": "HTTP GET /api/users",
              "kind": 2,
              "startTimeUnixNano": "{{nowNano}}",
              "endTimeUnixNano": "{{nowNano}}",
              "status": {
                "code": 1
              },
              "attributes": [
                {
                  "key": "http.method",
                  "value": {
                    "stringValue": "GET"
                  }
                },
                {
                  "key": "http.status_code",
                  "value": {
                    "intValue": 200
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Responses

200 – All accepted

null
boltTry it
env
POSThttp://localhost:5050/v1/traces

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/v1/traces' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "resourceSpans": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeSpans": [
        {
          "spans": [
            {
              "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
              "spanId": "00f067aa0ba902b7",
              "name": "HTTP GET /api/users",
              "kind": 2,
              "startTimeUnixNano": "{{nowNano}}",
              "endTimeUnixNano": "{{nowNano}}",
              "status": {
                "code": 1
              },
              "attributes": [
                {
                  "key": "http.method",
                  "value": {
                    "stringValue": "GET"
                  }
                },
                {
                  "key": "http.status_code",
                  "value": {
                    "intValue": 200
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}'
POST/v1/metrics

OTLP/HTTP – ingest metrics (JSON)

Send OpenTelemetry metrics to BugWatch via OTLP/HTTP.

Scope: ingest:write

Content-Type options

  • application/json — JSON body (used here)
  • application/x-protobuf — protobuf ExportMetricsServiceRequest (used by the OTel Collector)

Request body (JSON form)

{
  "resourceMetrics": [{
    "resource": { "attributes": [{ "key": "service.name", "value": { "stringValue": "my-service" } }] },
    "scopeMetrics": [{
      "metrics": [{
        "name": "http.server.request.duration",
        "unit": "ms",
        "gauge": {
          "dataPoints": [{
            "timeUnixNano": "1750924800000000000",
            "asDouble": 123.4,
            "attributes": [{ "key": "http.method", "value": { "stringValue": "GET" } }]
          }]
        }
      }]
    }]
  }]
}

Success — 200 OK (empty on full success; partialSuccess on rejection)

Headers

x-api-key

Request body

application/json
{
  "resourceMetrics": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeMetrics": [
        {
          "metrics": [
            {
              "name": "http.server.request.duration",
              "unit": "ms",
              "gauge": {
                "dataPoints": [
                  {
                    "timeUnixNano": "{{nowNano}}",
                    "asDouble": 123.4,
                    "attributes": [
                      {
                        "key": "http.method",
                        "value": {
                          "stringValue": "GET"
                        }
                      }
                    ]
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}

Responses

200 – All accepted

null
boltTry it
env
POSThttp://localhost:5050/v1/metrics

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/v1/metrics' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "resourceMetrics": [
    {
      "resource": {
        "attributes": [
          {
            "key": "service.name",
            "value": {
              "stringValue": "my-service"
            }
          }
        ]
      },
      "scopeMetrics": [
        {
          "metrics": [
            {
              "name": "http.server.request.duration",
              "unit": "ms",
              "gauge": {
                "dataPoints": [
                  {
                    "timeUnixNano": "{{nowNano}}",
                    "asDouble": 123.4,
                    "attributes": [
                      {
                        "key": "http.method",
                        "value": {
                          "stringValue": "GET"
                        }
                      }
                    ]
                  }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}'
POST/api/v1/prom/write

Prometheus remote-write (DOCUMENTATION ONLY)

Prometheus remote-write ingestion endpoint.

Scope: ingest:write

This endpoint is NOT called directly by humans. It is invoked by the Prometheus remote-write adapter or the bugwatch-otel-collector. The body is a Snappy-compressed protobuf WriteRequest — you cannot send a readable JSON body here.

To use Prometheus remote-write with BugWatch:

  1. Deploy the bugwatch-otel-collector (or configure Prometheus remote_write)
  2. Set the remote-write URL to: {{baseUrl}}/api/v1/prom/write
  3. Add header: x-api-key: sk_live_KEYID:secret

Success — 204 No Content

Common errors

  • 400 — malformed Snappy/protobuf body
  • 401 — invalid API key
  • 429 — rate limited

Headers

x-api-key
X-Prometheus-Remote-Write-Version

Request body

text/plain
<<< BINARY SNAPPY-COMPRESSED PROTOBUF — sent by Prometheus/collector, not a manual JSON request >>>

Responses

204 – Accepted

null
boltTry it
env
POSThttp://localhost:5050/api/v1/prom/write

Headers

x-api-key
X-Prometheus-Remote-Write-Version

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/prom/write' \
  -H 'Content-Type: application/json' \
  --data-raw '<<< BINARY SNAPPY-COMPRESSED PROTOBUF — sent by Prometheus/collector, not a manual JSON request >>>'
GET/api/v1/prom/query

Prometheus query (instant)

Query stored Prometheus metrics using a PromQL-compatible selector.

Scope: ingest:write (same key used for write is accepted for query)

Query params

  • query (required) — metric name or label selector e.g. http_requests_total or {service="api-gateway"}
  • time (optional) — Unix timestamp (seconds, float); defaults to now

Success — 200 OK

{
  "status": "success",
  "data": {
    "resultType": "vector",
    "result": [{ "metric": { "__name__": "http_requests_total", "service": "api" }, "value": [1750924800, "42"] }]
  }
}

Headers

x-api-key

Parameters

queryquerystringdefault: Metric name or label selector
timequerystringdefault: Unix timestamp seconds (optional, defaults to now)

Responses

200 – Instant vector

{
  "status": "success",
  "data": {
    "resultType": "vector",
    "result": [
      {
        "metric": {
          "__name__": "http_requests_total",
          "service": "api"
        },
        "value": [
          1785803160,
          "42"
        ]
      }
    ]
  }
}
boltTry it
env
GEThttp://localhost:5050/api/v1/prom/query

Query parameters

query
time

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/v1/prom/query'
GET/api/v1/prom/query_range

Prometheus query_range (over time)

Query stored Prometheus metrics over a time range.

Scope: ingest:write

Query params

  • query (required) — metric selector
  • start (required) — range start as Unix seconds (float)
  • end (required) — range end as Unix seconds (float)

Success — 200 OK

{
  "status": "success",
  "data": {
    "resultType": "matrix",
    "result": [{ "metric": { "__name__": "http_requests_total" }, "values": [[1750924800, "42"], [1750924860, "47"]] }]
  }
}

Headers

x-api-key

Parameters

queryquerystringdefault: Metric name or label selector
startquerystringdefault: Range start Unix seconds
endquerystringdefault: Range end Unix seconds

Responses

200 – Matrix result

{
  "status": "success",
  "data": {
    "resultType": "matrix",
    "result": [
      {
        "metric": {
          "__name__": "http_requests_total"
        },
        "values": [
          [
            1785799560,
            "38"
          ],
          [
            1785803160,
            "42"
          ]
        ]
      }
    ]
  }
}
boltTry it
env
GEThttp://localhost:5050/api/v1/prom/query_range

Query parameters

query
start
end

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/v1/prom/query_range'
GET/api/v1/prom/label/__name__/values

Prometheus label __name__ values (list metric names)

List all distinct metric names stored for this project+environment.

Scope: ingest:write

Success — 200 OK

{ "status": "success", "data": ["http_requests_total", "rum.lcp", "rum.cls"] }

Headers

x-api-key

Responses

200 – Metric names

{
  "status": "success",
  "data": [
    "http_requests_total",
    "rum.lcp",
    "rum.cls",
    "rum.inp"
  ]
}
boltTry it
env
GEThttp://localhost:5050/api/v1/prom/label/__name__/values

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/v1/prom/label/__name__/values'
GET/jaeger/api/services

Jaeger – list service names

List distinct service names that have sent traces to this BugWatch project.

Scope: ingest:write (same API key)

Success — 200 OK

{ "data": ["api-gateway", "payment-service", "user-service"], "total": 3, "limit": 3, "offset": 0, "errors": null }

Headers

x-api-key

Responses

200 – Services

{
  "data": [
    "api-gateway",
    "payment-service",
    "user-service"
  ],
  "total": 3,
  "limit": 3,
  "offset": 0,
  "errors": null
}
boltTry it
env
GEThttp://localhost:5050/jaeger/api/services

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/jaeger/api/services'
GET/jaeger/api/traces

Jaeger – list traces

List recent distributed traces for this project+environment.

Scope: ingest:write

Query params

  • service (optional) — filter by service name
  • limit (optional, default 20, max 100)
  • lookback (optional) — 1h | 24h | 7d | 30d (default 1h)

Success — 200 OK — returns Jaeger-compatible trace list with spans.

Headers

x-api-key

Parameters

limitquerystringdefault: Max traces (default 20, max 100)
lookbackquerystringdefault: 1h | 24h | 7d | 30d

Responses

200 – Traces list

{
  "data": [],
  "total": 0,
  "limit": 20,
  "offset": 0,
  "errors": null
}
boltTry it
env
GEThttp://localhost:5050/jaeger/api/traces

Query parameters

limit
lookback

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/jaeger/api/traces'
POST/api/v1/rum

RUM – ingest web-vitals

Ingest Real User Monitoring (RUM) web-vitals metrics (LCP, CLS, INP, FID, TTFB).

Auth: Either x-bugwatch-session (browser) or x-api-key (server-side / testing)

Scope (for x-api-key): ingest:write

Request body — three accepted shapes:

  1. Wrapped array:
{ "vitals": [{ "name": "LCP", "value": 1250.5, "url": "https://example.com/", "rating": "good" }] }
  1. Bare array:
[{ "name": "CLS", "value": 0.05, "rating": "good" }, { "name": "INP", "value": 180, "rating": "needs-improvement" }]
  1. Single vital object:
{ "name": "TTFB", "value": 320, "url": "https://example.com/checkout", "rating": "needs-improvement" }

Vital field schema:

FieldTypeNotes
namestring (required)LCP, CLS, INP, FID, TTFB
valuenumber (required)Metric value in ms (or unitless for CLS)
urlstring (optional)Page URL
ratingstring (optional)good
idstring (optional)Client-side unique id

Success — 202 Accepted

{ "ingested": 2 }

Stored as rum.<name_lower> metric points (e.g. rum.lcp, rum.cls).

Common errors

  • 400 — malformed JSON
  • 401 — invalid session token or API key
  • 403 — origin not in allowedOrigins (when using session token)
  • 429 — rate limited

Headers

x-api-keyOr use x-bugwatch-session for browser calls

Request body

application/json
{
  "vitals": [
    {
      "name": "LCP",
      "value": 1250.5,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "CLS",
      "value": 0.05,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "INP",
      "value": 180,
      "url": "https://example.com/",
      "rating": "needs-improvement"
    }
  ]
}

Responses

202 – Vitals ingested

{
  "ingested": 3
}
boltTry it
env
POSThttp://localhost:5050/api/v1/rum

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/rum' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "vitals": [
    {
      "name": "LCP",
      "value": 1250.5,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "CLS",
      "value": 0.05,
      "url": "https://example.com/",
      "rating": "good"
    },
    {
      "name": "INP",
      "value": 180,
      "url": "https://example.com/",
      "rating": "needs-improvement"
    }
  ]
}'

6 · Support Tickets

4 endpoints
GET/api/v1/support-tickets/tickets

List customer tickets

List tickets for a customer, identified by email.

Scope: full-access or ticket-management or read-only

Query params

ParamTypeNotes
customerEmailstring (required, email)Customer to fetch tickets for
statusstring (optional)OPEN
pageinteger (optional, min 1, default 1)Page number
limitinteger (optional, 1–100, default 20)Items per page

Success — 200 OK returns paginated ticket list.

Headers

x-api-key

Parameters

customerEmailquerystringdefault: Customer email (required)
pagequerystringdefault: Page number (default 1)
limitquerystringdefault: Items per page (1–100, default 20)

Responses

200 – Tickets list

{
  "tickets": [
    {
      "ticketId": "tkt_abc123",
      "title": "Unable to export invoice PDF",
      "status": "OPEN",
      "priority": "HIGH",
      "createdAt": "2026-06-26T10:00:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}
boltTry it
env
GEThttp://localhost:5050/api/v1/support-tickets/tickets

Query parameters

customerEmail
page
limit

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/v1/support-tickets/tickets'
POST/api/v1/support-tickets/tickets

Create ticket

Create a support ticket on behalf of a customer. Auto-creates the customer record if not found.

Scope: full-access or ticket-management

Request body

FieldTypeNotes
titlestring (required, max 200)Ticket title
descriptionstring (required, max 5000)Full description
customerNamestring (required, max 100)Customer full name
customerEmailstring (required, email)Customer email
prioritystring (optional)LOW
categorystring (optional, max 100)Ticket category

Success — 201 Created

{ "ticketId": "tkt_abc123", "status": "OPEN", "createdAt": "2026-06-26T10:00:00.000Z" }

The test script saves ticketId to the environment automatically.

Headers

x-api-key

Request body

application/json
{
  "title": "Unable to export invoice PDF",
  "description": "When I click \"Export as PDF\" on any invoice page, I get a blank file. Tested on Chrome 125 and Firefox 128. The file is 0 bytes. My account ID is ACC-4471.",
  "customerName": "Alice Smith",
  "customerEmail": "alice@example.com",
  "priority": "HIGH",
  "category": "Billing"
}

Responses

Successful response

null
boltTry it
env
POSThttp://localhost:5050/api/v1/support-tickets/tickets

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/support-tickets/tickets' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "title": "Unable to export invoice PDF",
  "description": "When I click \"Export as PDF\" on any invoice page, I get a blank file. Tested on Chrome 125 and Firefox 128. The file is 0 bytes. My account ID is ACC-4471.",
  "customerName": "Alice Smith",
  "customerEmail": "alice@example.com",
  "priority": "HIGH",
  "category": "Billing"
}'
POST/api/v1/support-tickets/tickets/comments

Add customer comment

Add a public comment from the customer to a ticket.

Scope: full-access or ticket-management

Side effects:

  • Auto-reopens RESOLVED tickets to IN_PROGRESS
  • Cannot comment on CLOSED tickets (returns 422)

Path param: ticketId (auto-filled from environment)

Request body

FieldTypeNotes
customerEmailstring (required, email)Ownership verification
contentstring (required, max 5000)Comment text
attachmentsarray (optional, max 5)Each item: URI string or {url, name} object

Success — 201 Created

{ "commentId": "cmt_abc123", "createdAt": "2026-06-26T10:05:00.000Z" }

Headers

x-api-key

Request body

application/json
{
  "customerEmail": "alice@example.com",
  "content": "I tried on Edge as well and the PDF is still blank. I noticed it only happens for invoices older than 90 days. Recent invoices export fine.",
  "attachments": [
    {
      "url": "https://storage.example.com/screenshots/blank-pdf.png",
      "name": "blank-pdf.png"
    }
  ]
}

Responses

Successful response

null
boltTry it
env
POSThttp://localhost:5050/api/v1/support-tickets/tickets/comments

Headers

x-api-key

Request body

Code samples

curl -X POST 'http://localhost:5050/api/v1/support-tickets/tickets/comments' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "customerEmail": "alice@example.com",
  "content": "I tried on Edge as well and the PDF is still blank. I noticed it only happens for invoices older than 90 days. Recent invoices export fine.",
  "attachments": [
    {
      "url": "https://storage.example.com/screenshots/blank-pdf.png",
      "name": "blank-pdf.png"
    }
  ]
}'

7 · Secret Manager

2 endpoints
GET/api/v1/secret-manager/apps/master-key

Get encrypted master key

Fetch the encrypted Master Encryption Key (MEK) for an app.

Scope: full-access or secret-read

Path param: appId — the App ID from the secret manager dashboard (set as {{appId}} in your environment)

Success — 200 OK

{
  "encryptedMek": "<base64-encoded-encrypted-key>",
  "algorithm": "AES-256-GCM",
  "kdfAlgorithm": "HKDF-SHA256"
}

Decrypt the MEK using your App Secret:

const mek = await crypto.subtle.decrypt(
  { name: 'AES-GCM', iv: decodeBase64(iv) },
  await deriveKey(appSecret),  // HKDF-SHA256
  decodeBase64(encryptedMek)
);

Common errors

  • 401 — invalid API key
  • 403 — key lacks secret-read scope
  • 404 — app not found
  • 429 — rate limited (50 req/hour)

Headers

x-api-key

Responses

200 – Encrypted MEK

{
  "encryptedMek": "base64encodedEncryptedKeyHere==",
  "algorithm": "AES-256-GCM",
  "kdfAlgorithm": "HKDF-SHA256"
}
boltTry it
env
GEThttp://localhost:5050/api/v1/secret-manager/apps/master-key

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/v1/secret-manager/apps/master-key'
GET/api/v1/secret-manager/apps/variables

Get encrypted variables

Fetch encrypted environment variables for an app.

Scope: full-access or secret-read

Path param: appId — App ID from dashboard Query param: environment (optional) — development | staging | production (default: development)

Success — 200 OK

{
  "environment": "production",
  "variables": [
    { "key": "DATABASE_URL", "encryptedValue": "<base64>", "iv": "<base64>" },
    { "key": "STRIPE_SECRET_KEY", "encryptedValue": "<base64>", "iv": "<base64>" }
  ]
}

Decrypt each variable using the MEK from GET master-key:

const plaintext = await crypto.subtle.decrypt(
  { name: 'AES-GCM', iv: decodeBase64(variable.iv) },
  mek,
  decodeBase64(variable.encryptedValue)
);

Common errors

  • 401 — invalid API key
  • 403 — key lacks secret-read scope
  • 404 — app not found or no variables for this environment

Headers

x-api-key

Parameters

environmentquerystringdefault: development | staging | production (default: development)

Responses

200 – Encrypted variables

{
  "environment": "production",
  "variables": [
    {
      "key": "DATABASE_URL",
      "encryptedValue": "base64ciphertext==",
      "iv": "base64iv=="
    },
    {
      "key": "STRIPE_SECRET_KEY",
      "encryptedValue": "base64ciphertext2==",
      "iv": "base64iv2=="
    }
  ]
}
boltTry it
env
GEThttp://localhost:5050/api/v1/secret-manager/apps/variables

Query parameters

environment

Headers

x-api-key

Code samples

curl -X GET 'http://localhost:5050/api/v1/secret-manager/apps/variables'