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
| Field | Type | Required | Max size | Default | Description |
|---|---|---|---|---|---|
title | string | Yes | 500 chars | — | Bug report title |
description | string | No | 10,000 chars | "" | Detailed description |
severity | enum | No | — | "medium" | "critical", "high", "medium", "low" |
url | string | No | 2,000 chars | — | Page URL where the bug occurred |
userAgent | string | No | 500 chars | — | Browser user agent string |
viewport | string | No | 50 chars | — | Viewport dimensions, e.g. "1920x1080" |
events | Event[] | No | 1,000 items | [] | Captured browser events |
screenshot | any | No | — | null | Legacy field; ignored. Occurrence records not_stored. Never a ready file |
audio | any | No | — | null | Legacy field; ignored. Occurrence records not_stored. Never a ready file |
aiSummary | object | No | — | null | AI-generated analysis |
encrypted | object | No | — | — | Unsupported end-to-end; do not send secrets here |
reporterEmail | string | No | valid email | — | Reporter email for attribution |
reporterId | string | No | 200 chars | — | App-specific reporter identifier |
sessionId | string | No | 200 chars | — | Session identifier for occurrence grouping |
userId | string | No | 200 chars | — | End-user or account identifier |
release | string | No | 200 chars | — | Release, build, or deploy version (app version on mobile). Omitted release is a structured miss (release_missing), never HTTP 400 |
stack | string | No | 10,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 |
frames | object[] | No | 200 items | — | Optional parsed frames; stored on occurrence metadata.frames when present |
mcp_timeline | object[] | No | 500 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 |
environment | string | No | 100 chars | — | Environment label such as production or staging |
platform | string | No | 40 chars | inferred | Capture 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.bugtapeTokens | object | No | 96,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. |
device | string | No | 120 chars | — | Device model, e.g. iPhone15,3 (stored in occurrence metadata.device) |
osVersion | string | No | 60 chars | — | OS version, e.g. 17.4.1 (stored in occurrence metadata.osVersion) |
metadata | object | No | — | {} | Additional occurrence metadata for live feed and triage |
Event schema
Each event in the events array:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | Yes | "unknown" | Event type identifier |
timestamp | number | No | now | Unix timestamp in milliseconds |
data | any | No | — | 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
| Field | Max size | Truncation behavior |
|---|---|---|
title | 500 chars | Truncated to 500 characters |
description | 10,000 chars | Truncated to 10,000 characters |
url | 2,000 chars | Truncated to 2,000 characters |
userAgent | 500 chars | Rejected if > 500 |
viewport | 50 chars | Rejected if > 50 |
events | 1,000 items | First 1,000 accepted event objects retained |
Event data | No hard limit | Stored as JSONB; keep individual event data under 100KB |
| Total body | 10 MB after decompression | 413 if the inflated JSON exceeds 10 MB (10 × 1024 × 1024). gzip, deflate and br stop at that same limit |
| Console args | 2,000 chars/arg | Truncated 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'sreleasewith 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
| Plan | Bugs per month | Projects | Integrations | Team members |
|---|---|---|---|---|
| Free | 100 | 1 | 1 | 3 |
| Pro | 5,000 | Unlimited | 10 | 10 |
| Team | 25,000 | Unlimited | 50 | 50 |
| Enterprise | Unlimited | Unlimited | Unlimited | Unlimited |
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.