New Instance Platform
v1.0.0New 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
- Import this collection into your API client or documentation tool
- Import the matching environment file (
NewInstance.local,NewInstance.staging, orNewInstance.production) - Select the environment from the dropdown (top-right)
- 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/api/auth/verifyVerify 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 revoked403— key found but inactive
Headers
x-api-keyResponses
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"
}http://localhost:5050/api/auth/verifyHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/auth/verify'1 · BugWatch — Ingest & Errors
2 endpoints/api/v1/bugwatch/browser-sessionMint 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/missingx-api-key403— key lacksingest:writescope
Headers
x-api-keyResponses
200 – Token minted
{
"token": "eyJhbGciOiJIUzI1NiJ9.example",
"expiresAt": "2026-06-26T12:15:00.000Z"
}http://localhost:5050/api/v1/bugwatch/browser-sessionHeaders
x-api-keyRequest body
Code samples
curl -X POST 'http://localhost:5050/api/v1/bugwatch/browser-session'/api/v1/bugwatch/ingestIngest – 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) orapplication/x-ndjson(one event per line) - Max body: 2 MB
Event fields
| Field | Type | Notes |
|---|---|---|
level | number | 10/20/30/40/50/60 |
time | number | Unix ms timestamp |
message | string | Human-readable text |
release | string | App build version |
environment | string | production / staging / development |
eventId | string | Optional; enables deduplication within 10-min window |
tags | object | Flat key-value labels (≤50 keys) |
user | object | {id, email, username, ip} — only these four keys; others are silently dropped |
traceId | string | Hex distributed trace id (≤32 chars) |
spanId | string | Span id |
exception | object | {type, value, stacktrace: {frames}} |
breadcrumbs | array | Leading events |
Success — 202 Accepted
{ "ingested": 1, "skipped": 0, "deduped": 0 }Headers
x-api-keyRequest 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
nullhttp://localhost:5050/api/v1/bugwatch/ingestHeaders
x-api-keyRequest 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/api/v1/bugwatch/ingest/browserBrowser 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/jsonorapplication/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 token403— origin not inallowedOrigins(origin_not_allowed)404— project not found or inactive
Headers
x-bugwatch-sessionRequest 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
}http://localhost:5050/api/v1/bugwatch/ingest/browserHeaders
x-bugwatch-sessionRequest 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 }
]
}
}
}'/api/v1/bugwatch/ingest/mobileMobile 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/jsonorapplication/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 token429— 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: 2with top-levelbinaryImages+nativeStacktrace(iOS) /nativeFrames(Android) — rawinstruction_addrvalues 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.stacktraceas 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-tokenRequest 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
}http://localhost:5050/api/v1/bugwatch/ingest/mobileHeaders
x-bugwatch-tokenRequest 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/api/v1/bugwatch/artifacts/presignArtifact 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)
artifactType | File | Platform |
|---|---|---|
r8 / proguard | mapping.txt | android |
sourcemap | .map | ios · react-native · android |
dart-symbols | --split-debug-info output | flutter |
dsym | Apple dSYM referenced as an artifact | ios |
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 r8The 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-keyResponses
Successful response
nullhttp://localhost:5050/api/v1/bugwatch/artifacts/presignHeaders
x-api-keyRequest body
Code samples
curl -X POST 'http://localhost:5050/api/v1/bugwatch/artifacts/presign'/api/v1/bugwatch/artifacts/uploads/completeArtifact upload · step 2 – complete
Step 2 of 2 — confirm the artifact landed and swap it in.
Scope: symbols:upload
What is verified
completeconfirms 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_UPLOADrow 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 touploadUrl.400— the stored size does not match what was declared at presign.404— no such upload for this project.
Headers
x-api-keyResponses
Successful response
nullhttp://localhost:5050/api/v1/bugwatch/artifacts/uploads/completeHeaders
x-api-keyRequest body
Code samples
curl -X POST 'http://localhost:5050/api/v1/bugwatch/artifacts/uploads/complete'/api/v1/bugwatch/debug-symbols/presignDebug 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)
| Platform | Accepted |
|---|---|
| ios · macos · tvos · watchos · visionos · catalyst | .zip of .dSYM bundles, .xcarchive dSYMs, or a raw Mach-O |
| android | Raw 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 r8The 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-keyResponses
Successful response
nullhttp://localhost:5050/api/v1/bugwatch/debug-symbols/presignHeaders
x-api-keyRequest body
Code samples
curl -X POST 'http://localhost:5050/api/v1/bugwatch/debug-symbols/presign'/api/v1/bugwatch/debug-symbols/uploads/completeDebug symbols · step 2 – complete
Step 2 of 2 — confirm the archive landed and queue it for indexing.
Scope: symbols:upload
What is verified
completeconfirms 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_UPLOADrow 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 touploadUrl.400— declared size does not match the stored object.
Headers
x-api-keyResponses
Successful response
nullhttp://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/completeHeaders
x-api-keyRequest body
Code samples
curl -X POST 'http://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/complete'/api/v1/bugwatch/debug-symbols/uploadsList 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 byQUEUED|PROCESSING|DONE|FAILED
Success — 200 OK returns { uploads: [...] }
Headers
x-api-keyParameters
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"
}
]
}http://localhost:5050/api/v1/bugwatch/debug-symbols/uploadsQuery parameters
limitHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/v1/bugwatch/debug-symbols/uploads'/api/v1/bugwatch/debug-symbols/uploads/reprocessReprocess – 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-keyResponses
200 – Re-queued
{
"requeued": 12
}http://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/reprocessHeaders
x-api-keyRequest body
Code samples
curl -X POST 'http://localhost:5050/api/v1/bugwatch/debug-symbols/uploads/reprocess'5 · BugWatch — OpenTelemetry, Prometheus & Tracing
10 endpoints/v1/logsOTLP/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— protobufExportLogsServiceRequest(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-keyRequest 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
nullhttp://localhost:5050/v1/logsHeaders
x-api-keyRequest 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"
}
}
]
}
]
}
]
}
]
}'/v1/tracesOTLP/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— protobufExportTraceServiceRequest(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-keyRequest 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
nullhttp://localhost:5050/v1/tracesHeaders
x-api-keyRequest 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
}
}
]
}
]
}
]
}
]
}'/v1/metricsOTLP/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— protobufExportMetricsServiceRequest(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-keyRequest 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
nullhttp://localhost:5050/v1/metricsHeaders
x-api-keyRequest 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"
}
}
]
}
]
}
}
]
}
]
}
]
}'/api/v1/prom/writePrometheus 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 protobufWriteRequest— you cannot send a readable JSON body here.
To use Prometheus remote-write with BugWatch:
- Deploy the
bugwatch-otel-collector(or configure Prometheusremote_write) - Set the remote-write URL to:
{{baseUrl}}/api/v1/prom/write - Add header:
x-api-key: sk_live_KEYID:secret
Success — 204 No Content
Common errors
400— malformed Snappy/protobuf body401— invalid API key429— rate limited
Headers
x-api-keyX-Prometheus-Remote-Write-VersionRequest body
text/plain<<< BINARY SNAPPY-COMPRESSED PROTOBUF — sent by Prometheus/collector, not a manual JSON request >>>Responses
204 – Accepted
nullhttp://localhost:5050/api/v1/prom/writeHeaders
x-api-keyX-Prometheus-Remote-Write-VersionRequest 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 >>>'/api/v1/prom/queryPrometheus 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_totalor{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-keyParameters
queryquerystringdefault: Metric name or label selectortimequerystringdefault: 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"
]
}
]
}
}http://localhost:5050/api/v1/prom/queryQuery parameters
querytimeHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/v1/prom/query'/api/v1/prom/query_rangePrometheus query_range (over time)
Query stored Prometheus metrics over a time range.
Scope: ingest:write
Query params
query(required) — metric selectorstart(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-keyParameters
queryquerystringdefault: Metric name or label selectorstartquerystringdefault: Range start Unix secondsendquerystringdefault: Range end Unix secondsResponses
200 – Matrix result
{
"status": "success",
"data": {
"resultType": "matrix",
"result": [
{
"metric": {
"__name__": "http_requests_total"
},
"values": [
[
1785799560,
"38"
],
[
1785803160,
"42"
]
]
}
]
}
}http://localhost:5050/api/v1/prom/query_rangeQuery parameters
querystartendHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/v1/prom/query_range'/api/v1/prom/label/__name__/valuesPrometheus 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-keyResponses
200 – Metric names
{
"status": "success",
"data": [
"http_requests_total",
"rum.lcp",
"rum.cls",
"rum.inp"
]
}http://localhost:5050/api/v1/prom/label/__name__/valuesHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/v1/prom/label/__name__/values'/jaeger/api/servicesJaeger – 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-keyResponses
200 – Services
{
"data": [
"api-gateway",
"payment-service",
"user-service"
],
"total": 3,
"limit": 3,
"offset": 0,
"errors": null
}http://localhost:5050/jaeger/api/servicesHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/jaeger/api/services'/jaeger/api/tracesJaeger – list traces
List recent distributed traces for this project+environment.
Scope: ingest:write
Query params
service(optional) — filter by service namelimit(optional, default 20, max 100)lookback(optional) —1h|24h|7d|30d(default1h)
Success — 200 OK — returns Jaeger-compatible trace list with spans.
Headers
x-api-keyParameters
limitquerystringdefault: Max traces (default 20, max 100)lookbackquerystringdefault: 1h | 24h | 7d | 30dResponses
200 – Traces list
{
"data": [],
"total": 0,
"limit": 20,
"offset": 0,
"errors": null
}http://localhost:5050/jaeger/api/tracesQuery parameters
limitlookbackHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/jaeger/api/traces'/api/v1/rumRUM – 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:
- Wrapped array:
{ "vitals": [{ "name": "LCP", "value": 1250.5, "url": "https://example.com/", "rating": "good" }] }- Bare array:
[{ "name": "CLS", "value": 0.05, "rating": "good" }, { "name": "INP", "value": 180, "rating": "needs-improvement" }]- Single vital object:
{ "name": "TTFB", "value": 320, "url": "https://example.com/checkout", "rating": "needs-improvement" }Vital field schema:
| Field | Type | Notes |
|---|---|---|
name | string (required) | LCP, CLS, INP, FID, TTFB |
value | number (required) | Metric value in ms (or unitless for CLS) |
url | string (optional) | Page URL |
rating | string (optional) | good |
id | string (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 JSON401— invalid session token or API key403— origin not inallowedOrigins(when using session token)429— rate limited
Headers
x-api-keyOr use x-bugwatch-session for browser callsRequest 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
}http://localhost:5050/api/v1/rumHeaders
x-api-keyRequest 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/api/v1/support-tickets/auth/login-linksMint customer sign-in link
Mint a single-use support-portal sign-in link for a customer your backend has already authenticated.
Returns a URL that signs the customer into the support portal without a password. The link expires in 10 minutes and is single-use.
Scope: full-access or ticket-management
Request body
| Field | Type | Notes |
|---|---|---|
email | string (required) | Customer email — portal identity |
name | string (optional, max 100) | Customer display name |
externalId | string (optional, max 200) | Your internal customer ID |
returnTo | string (optional, max 500) | Portal path to land on (must start with /) |
Success — 200 OK
{ "url": "https://support.yourcompany.com/auth/redeem?token=abc123", "expiresAt": "2026-06-26T10:10:00.000Z" }Headers
x-api-keyRequest body
application/json{
"email": "alice@example.com",
"name": "Alice Smith",
"externalId": "cust_789",
"returnTo": "/tickets"
}Responses
200 – Link minted
{
"url": "https://support.yourcompany.com/auth/redeem?token=abc123",
"expiresAt": "2026-06-26T10:10:00.000Z"
}http://localhost:5050/api/v1/support-tickets/auth/login-linksHeaders
x-api-keyRequest body
Code samples
curl -X POST 'http://localhost:5050/api/v1/support-tickets/auth/login-links' \
-H 'Content-Type: application/json' \
--data-raw '{
"email": "alice@example.com",
"name": "Alice Smith",
"externalId": "cust_789",
"returnTo": "/tickets"
}'/api/v1/support-tickets/ticketsList customer tickets
List tickets for a customer, identified by email.
Scope: full-access or ticket-management or read-only
Query params
| Param | Type | Notes |
|---|---|---|
customerEmail | string (required, email) | Customer to fetch tickets for |
status | string (optional) | OPEN |
page | integer (optional, min 1, default 1) | Page number |
limit | integer (optional, 1–100, default 20) | Items per page |
Success — 200 OK returns paginated ticket list.
Headers
x-api-keyParameters
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
}http://localhost:5050/api/v1/support-tickets/ticketsQuery parameters
customerEmailpagelimitHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/v1/support-tickets/tickets'/api/v1/support-tickets/ticketsCreate 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
| Field | Type | Notes |
|---|---|---|
title | string (required, max 200) | Ticket title |
description | string (required, max 5000) | Full description |
customerName | string (required, max 100) | Customer full name |
customerEmail | string (required, email) | Customer email |
priority | string (optional) | LOW |
category | string (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-keyRequest 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
nullhttp://localhost:5050/api/v1/support-tickets/ticketsHeaders
x-api-keyRequest 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"
}'/api/v1/support-tickets/tickets/commentsAdd customer comment
Add a public comment from the customer to a ticket.
Scope: full-access or ticket-management
Side effects:
- Auto-reopens
RESOLVEDtickets toIN_PROGRESS - Cannot comment on
CLOSEDtickets (returns 422)
Path param: ticketId (auto-filled from environment)
Request body
| Field | Type | Notes |
|---|---|---|
customerEmail | string (required, email) | Ownership verification |
content | string (required, max 5000) | Comment text |
attachments | array (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-keyRequest 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
nullhttp://localhost:5050/api/v1/support-tickets/tickets/commentsHeaders
x-api-keyRequest 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/api/v1/secret-manager/apps/master-keyGet 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 key403— key lackssecret-readscope404— app not found429— rate limited (50 req/hour)
Headers
x-api-keyResponses
200 – Encrypted MEK
{
"encryptedMek": "base64encodedEncryptedKeyHere==",
"algorithm": "AES-256-GCM",
"kdfAlgorithm": "HKDF-SHA256"
}http://localhost:5050/api/v1/secret-manager/apps/master-keyHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/v1/secret-manager/apps/master-key'/api/v1/secret-manager/apps/variablesGet 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 key403— key lackssecret-readscope404— app not found or no variables for this environment
Headers
x-api-keyParameters
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=="
}
]
}http://localhost:5050/api/v1/secret-manager/apps/variablesQuery parameters
environmentHeaders
x-api-keyCode samples
curl -X GET 'http://localhost:5050/api/v1/secret-manager/apps/variables'