Home

BugTape docs

Ingest Payload Schema

POST /v1/ingest fields and receipts.

Detailed specification for the POST /v1/ingest request and response.

Request schema

Capture identity and retry safety

The SDK sends schemaVersion: 1 and a UUID captureId generated once per captured report. Keep the entire accepted payload unchanged across retries. An identical retry in the authenticated project returns the original acceptance, including occurrenceId, without creating another occurrence, event or charge. The response includes projectId derived from the current authenticated project, including when replaying older receipts that did not store that field. A changed payload with that ID returns 409 CAPTURE_ID_CONFLICT. Fresh API-key checks still apply. Legacy clients without an ID retain their previous submission behavior.

Optional fields: capturedAt (ISO8601 with timezone), sdkName, sdkVersion, captureSource, osFamily, framework, runtime. The server adds receivedAt. applicationId, buildId and installationId must refer to server registry records in the key's project; build/install IDs also require the matching application. These labels do not grant CI/agent authority. Registry management routes are not yet released.

Top-level fields

FieldTypeRequiredMax sizeDefaultDescription
titlestringYes500 chars—Bug report title
descriptionstringNo10,000 chars""Detailed description
severityenumNo—"medium""critical", "high", "medium", "low"
urlstringNo2,000 chars—Page URL where the bug occurred
userAgentstringNo500 chars—Browser user agent string
viewportstringNo50 chars—Viewport dimensions, e.g. "1920x1080"
eventsEvent[]No1,000 items[]Captured browser events
screenshotanyNo—nullLegacy field; ignored. Occurrence records not_stored. Never a ready file
audioanyNo—nullLegacy field; ignored. Occurrence records not_stored. Never a ready file
aiSummaryobjectNo—nullAI-generated analysis
encryptedobjectNo——Unsupported end-to-end; do not send secrets here
reporterEmailstringNovalid email—Reporter email for attribution
reporterIdstringNo200 chars—App-specific reporter identifier
sessionIdstringNo200 chars—Session identifier for occurrence grouping
userIdstringNo200 chars—End-user or account identifier
releasestringNo200 chars—Release, build, or deploy version (app version on mobile). Omitted release is a structured miss (release_missing), never HTTP 400
stackstringNo10,000 chars—Optional stack for server / non-browser captures. Stored on occurrence metadata.stack and as an error event when events omit stack. Omitted stack is a structured miss (stack_missing), never HTTP 400
framesobject[]No200 items—Optional parsed frames; stored on occurrence metadata.frames when present
mcp_timelineobject[]No500 items—Optional MCP timeline retained on occurrence metadata.mcp_timeline and kept when a failure pack is stored without its own timeline. Ingest does not create packs
environmentstringNo100 chars—Environment label such as production or staging
platformstringNo40 charsinferredCapture platform: web, ios, android, react-native, flutter, server, other. Aliases (iPhone, node, expo, …) are normalised. Inferred from userAgent when omitted (browser UA → web, CFNetwork → ios, no UA → server). On the v1 web/server path, platform is not part of the fingerprint — one issue can span platforms. Native v2 fingerprints hash a canonical JSON blob that includes platform plus application/build/site
metadata.bugtapeTokensobjectNo96,000-char ct—Sealed client-detail vault from the browser SDK (tokenize). Kept on the occurrence as metadata.clientTokens only when it has the exact vault shape; anything else is dropped. BugTape cannot open it.
devicestringNo120 chars—Device model, e.g. iPhone15,3 (stored in occurrence metadata.device)
osVersionstringNo60 chars—OS version, e.g. 17.4.1 (stored in occurrence metadata.osVersion)
metadataobjectNo—{}Additional occurrence metadata for live feed and triage

Event schema

Each event in the events array:

FieldTypeRequiredDefaultDescription
typestringYes"unknown"Event type identifier
timestampnumberNonowUnix timestamp in milliseconds
dataanyNo—Event-specific payload

Common event types and their data shapes

Error events

// type: "error"
{
  message: string;     // Error message
  filename: string;    // Source file URL
  lineno: number;      // Line number
  colno: number;       // Column number
  stack: string;       // Full stack trace
}

// type: "error:unhandledrejection"
{
  message: string;
  stack: string;
}

Console events

// type: "console:log" | "console:warn" | "console:error" | "console:info" | "console:debug"
{
  level: string;
  args: string[];      // Serialized arguments (max 2,000 chars each)
}

Network events

// type: "network" (resource timing)
{
  url: string;
  duration: number;    // milliseconds
  transferSize: number;
  initiatorType: string;
}

// type: "network:fetch"
{
  url: string;
  method: string;      // GET, POST, etc.
  status: number;      // HTTP status code
  duration: number;
}

// type: "network:fetch:error"
{
  url: string;
  method: string;
  error: string;
  duration: number;
}

// type: "network:xhr"
{
  url: string;
  method: string;
  status: number;
  duration: number;
}

Rage click events

// type: "rage-click"
{
  x: number;
  y: number;
  clicks: number;
  target: string;      // CSS selector or tag name
  url: string;
  innerText: string;
}

DOM events

// type: "dom"
// rrweb event objects — see https://github.com/rrweb-io/rrweb
// These are opaque to the API and stored as-is.

Evidence and encryption limits

This intake stores structured report fields and events. It does not persist the legacy screenshot, audio or encrypted fields as retrievable artifacts. Offered values are dropped and recorded on the occurrence as metadata.media.<field> = { stored: false, reason: "not_stored" }. is_encrypted is never set from a payload flag — ciphertext and key ownership do not exist on this path. A 201 receipt includes media: { ready: false, stored: false, notStored: [...] } and is not proof of media storage. The SDK sends approved PNG bytes through the separate evidence reservation/upload lifecycle only when capability discovery admits PNG. That path has local end-to-end qualification; hosted media storage remains unavailable (GET /v1/evidence/capabilities stays storage_unavailable until G9 is MET). Report and image retry identities stay fixed, and SDK success waits for a ready artifact. See the SDK README for storage limits. Client encryptionKey is rejected by the SDK because end-to-end ciphertext storage/read/decryption is not supported.

Response schema

Success (201 Created)

{
  id: string;              // UUID — stable report identifier
  projectId: string;       // UUID derived from the authenticated capture key
  occurrenceId: string;    // UUID of the accepted occurrence
  schemaVersion?: 1;       // Present with capture identity
  captureId?: string;      // Echoes the accepted capture UUID
  receivedAt?: string;     // Original server acceptance time
  fingerprint: string;     // 16-char hex — dedup key
  deduplicated: boolean;   // true if grouped with existing open bug
  regression: boolean;     // true if reopened a resolved bug
  is_regression: boolean;  // alias for regression
  unknown: boolean;        // true if a fixed issue recurred but BugTape cannot order its release
  release: string | null;  // the release stored on this occurrence
  warnings?: Array<'release_missing'>; // present only when something limits what BugTape can tell you
  media: {                 // Ingest never stores screenshot/audio/encrypted
    ready: false;
    stored: false;
    notStored: Array<'screenshot' | 'audio' | 'encrypted'>;
  };
}

Error responses

400 — Validation failed

{
  "error": "Validation failed",
  "details": [
    { "code": "too_small", "minimum": 1, "type": "string", "inclusive": true, "exact": false, "message": "String must contain at least 1 character(s)", "path": ["title"] }
  ]
}

400 — Malformed JSON

{ "error": "Invalid JSON in request body" }

401 — Auth failure

{ "error": "Missing or invalid API key" }

402 — Plan limit exceeded

{
  "error": "Plan limit reached",
  "limit": 100,
  "current": 100,
  "plan": "free",
  "upgradeUrl": "/console/#settings"
}

429 — Rate limited

The Retry-After header carries the same seconds as retryAfter.

{
  "error": "Too many requests",
  "retryAfter": 45
}

Field size and truncation guidance

FieldMax sizeTruncation behavior
title500 charsTruncated to 500 characters
description10,000 charsTruncated to 10,000 characters
url2,000 charsTruncated to 2,000 characters
userAgent500 charsRejected if > 500
viewport50 charsRejected if > 50
events1,000 itemsFirst 1,000 accepted event objects retained
Event dataNo hard limitStored as JSONB; keep individual event data under 100KB
Total body10 MB after decompression413 if the inflated JSON exceeds 10 MB (10 × 1024 × 1024). gzip, deflate and br stop at that same limit
Console args2,000 chars/argTruncated by SDK at capture time

Recommendation: If you buffer more than 1,000 events, prioritize errors, network failures, and console errors. Drop DOM mutation events first, as they are the most voluminous.

Deduplication behavior

Reports are deduplicated by fingerprint. Web/server (v1) uses:

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

Native (ios / android / react-native / flutter) uses v2: SHA256 of a canonical JSON failureSignature that includes version, platform, applicationId, buildId, signal, and the symbol site or image offset. That formula is different from v1. Do not send a native crash expecting it to merge with a web report of the same title.

Normalization rules:

  • Hex addresses (0xABCD) → <hex>
  • Timestamps (13-digit numbers) → <timestamp>
  • UUIDs → <uuid>
  • IP addresses → <ip>
  • Port numbers → :<port>
  • Large numbers (8+ digits) → <num>
  • URL path segments with numeric IDs → <id>

Matching behavior:

  • Same fingerprint + open bug → increments report_count (response: deduplicated: true)
  • Same fingerprint + resolved bug → reopens the bug (response: regression: true)
  • Same fingerprint + approved fix (awaiting_verification) anchored on a release → compares this report's release with the anchor. An older or equal release is expected noise. A newer release reopens the bug (regression: true). Dotted versions (1.4.2, v2.0) compare by number. Git SHAs and other build ids compare by when this project first reported them, so a release BugTape has never seen is newer. No release → unknown: true, not a regression.
  • New fingerprint → creates a new bug (response: deduplicated: false, regression: false)

warnings: ["release_missing"] means the report had no release. BugTape still accepts it, but it cannot tell a regression from old noise. Send the release on every report (release in the body or BUGTAPE_RELEASE for the server SDKs). A git SHA works.

Plan limits

PlanBugs per monthProjectsIntegrationsTeam members
Free100113
Pro5,000Unlimited1010
Team25,000Unlimited5050
EnterpriseUnlimitedUnlimitedUnlimitedUnlimited

A Free workspace with any prepaid credit balance (Prepaid credits) gets the Pro limits while it holds credits.

When the monthly bug limit is reached, the API returns 402 Payment Required.