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 returns409 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 |
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
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
{
"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 | Request rejected if body exceeds 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) - New fingerprint → creates a new bug (response:
deduplicated: false, regression: false)
Plan limits
| Plan | Bugs per month | Projects | Integrations | Team members |
|---|---|---|---|---|
| Free | 100 | 1 | 1 | 3 |
| Pro | 1,000 | 5 | 10 | 10 |
| Team | 5,000 | 20 | 50 | 50 |
| Enterprise | Unlimited | Unlimited | Unlimited | Unlimited |
When the monthly bug limit is reached, the API returns 402 Payment Required.