Ingestion API Reference
The BugsPulse ingestion API accepts sessions, events, crashes, and network logs from your SDK (or any HTTP client). All endpoints require a project API key.
Authentication
Pass your project API key in the Authorization header as a Bearer token. Keys are scoped to a single project and start with bp_.
Authorization: Bearer bp_your_api_key_hereRequests without a valid key return 401 Unauthorized. Requests with a valid key whose project API key has been deactivated (e.g. because the team owner was suspended) also return 401.
Base URL
https://api.bugspulse.comAll endpoints are prefixed with /ingest.
Sessions
/ingest/sessionStart a new session. Call once per app launch before sending events.
Request body
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000", // UUID v4
"device": {
"platform": "ios", // "ios" | "android" | "macos" | "linux" | "windows"
"osVersion": "17.4.1",
"model": "iPhone 15 Pro",
"brand": "Apple",
"appVersion": "2.4.1",
"appBuildNumber": "241",
"sdkVersion": "0.1.0",
"locale": "en-US",
"timezone": "America/New_York",
"screenWidth": 393, // logical pixels
"screenHeight": 852,
"networkType": "wifi" // "wifi" | "cellular" | "none" | "unknown"
},
"startedAt": 1735000000000, // Unix ms
"userId": "usr_7f2a3b" // optional — your internal user ID
}Response
201 Created
{ "ok": true }The endpoint is idempotent: sending the same sessionId twice does not create a duplicate. The second request is silently ignored (ON CONFLICT DO NOTHING).
/ingest/session/endMark a session as completed and record its duration.
Request body
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"endedAt": 1735000180000 // Unix ms
}Response
200 OK
{ "ok": true }The server computes duration = endedAt − startedAt and transitions the session to completed. If a fatal crash was already recorded for this session, the status stays crashed — it is never downgraded. Sessions that never receive an end call remain active and their duration is excluded from analytics.
200 (not 404) when the session does not exist. This makes retries safe — a delayed end event for an already-deleted session will not cause SDK errors.Events
/ingest/eventsSend a batch of up to 500 structured events for a session.
Request body
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"events": [...]
}Response
201 Created
{ "ok": true, "count": 3 }The session must belong to the authenticated project. A request referencing an unknown or cross-project session returns 404.
Event types
All events share a common envelope and add type-specific fields:
navigate — screen transition
{
"type": "navigate",
"sessionId": "...",
"timestamp": 1735000001000,
"sequenceNumber": 0,
"from": "Cart", // previous route name (null on first navigation)
"to": "Checkout"
}network — HTTP request
{
"type": "network",
"sessionId": "...",
"timestamp": 1735000002000,
"sequenceNumber": 1,
"method": "POST",
"url": "https://api.yourapp.com/checkout",
"statusCode": 500, // omit for connection errors
"duration": 1203, // milliseconds
"error": null // error string, or null
}/ingest/events are automatically mirrored into the network logs table — you do not need to call /ingest/network separately unless you prefer the dedicated endpoint.custom — your own events
{
"type": "custom",
"sessionId": "...",
"timestamp": 1735000003000,
"sequenceNumber": 2,
"name": "checkout_started",
"properties": {
"product_id": "sku_123",
"quantity": 2
}
}tap — user touch
{
"type": "tap",
"sessionId": "...",
"timestamp": 1735000004000,
"sequenceNumber": 3,
"x": 196, // logical pixels from left
"y": 452, // logical pixels from top
"target": "CheckoutButton"
}Sequence numbers
sequenceNumber is a monotonically increasing integer starting at 0 per session. It is used to order events correctly during replay even when batches arrive out of order. The SDK manages this counter automatically.
Crashes
/ingest/crashReport an unhandled crash or a handled exception.
Request body
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"message": "TypeError: Cannot read properties of undefined (reading 'id')",
"stackTrace": "at checkout (checkout.ts:42:15)
at handleTap (Button.tsx:18:3)
...",
"isFatal": true, // true = app crashed; false = handled exception
"handled": false, // true = caught by your try/catch
"timestamp": 1735000005000
}Response
201 Created
{ "ok": true }How crash grouping works
The server computes a SHA-256 hash of the first five lines of the stack trace and uses it as a group key. Crashes with the same group key are merged into a single crash group in the dashboard — their occurrence counter increments automatically. Only the first occurrence triggers the AI summary and crash-alert email.
A fatal crash (isFatal: true) transitions the session status to crashed.
Network logs
/ingest/networkSend a batch of up to 200 network requests directly to the network logs table.
Request body
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"requests": [
{
"id": "req_uuid_v4",
"method": "GET",
"url": "https://api.yourapp.com/products",
"statusCode": 200,
"duration": 312, // milliseconds
"requestSize": 0, // bytes, optional
"responseSize": 4096, // bytes, optional
"error": null,
"timestamp": 1735000006000
},
{
"id": "req_uuid_v4_2",
"method": "POST",
"url": "https://payments.example.com/charge",
"statusCode": 500,
"duration": 1840,
"error": "Internal Server Error",
"timestamp": 1735000007000
}
]
}Response
201 Created
{ "ok": true, "count": 2 }Requests with a statusCode >= 400 or a non-null error field automatically flag the session as having network errors, which shows the warning badge in the sessions list.
Export API
Need to pull your data into a warehouse or BI tool? See the dedicated Export API reference for full documentation on /export/sessions, /export/crashes, /export/events, and /export/network.
Batching guidelines
| Endpoint | Max items per request | Recommended flush interval |
|---|---|---|
| /ingest/events | 500 events | 5 seconds (SDK default) |
| /ingest/network | 200 requests | Flush at session end or every 30 s |
Rate limits
| Route prefix | Limit | Window |
|---|---|---|
| /ingest/* | 600 requests | per API key / per minute |
| /api/auth/* | 20 requests | per IP / per minute |
Exceeding the limit returns 429 Too Many Requests. Use exponential backoff starting at 1 s and capping at 60 s for retry logic.
Error responses
All error responses share the same shape:
{
"error": "Human-readable message",
"details": [...] // present on 422 validation errors only
}| Status | Meaning | Retry? |
|---|---|---|
| 200 | Session end accepted (idempotent) | No |
| 201 | Data created and accepted | No |
| 400 | Malformed JSON or missing required fields | No — fix payload |
| 401 | Missing or invalid API key | No — check key |
| 404 | Session not found or belongs to another project | No — check sessionId |
| 422 | Schema validation failed — see error.details | No — fix payload |
| 429 | Rate limited | Yes — exponential backoff |
| 500 | Server error | Yes — safe to retry |
Retry strategy
The official SDKs retry 429 and 5xx responses up to 3 times with exponential backoff (2 s, 4 s, 8 s). Client errors (4xx except 429) are not retried — they indicate a bug in the payload that retrying will not fix.
All ingest endpoints are idempotent for the same payload, so retrying a 500 response that was actually committed server-side will not create duplicates.
Testing with curl
Send a test session start to verify your API key is working:
curl -X POST https://api.bugspulse.com/ingest/session \
-H "Authorization: Bearer bp_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "test-session-001",
"device": {
"platform": "android",
"osVersion": "14",
"model": "Pixel 8",
"brand": "Google",
"appVersion": "1.0.0",
"appBuildNumber": "1",
"sdkVersion": "0.1.0",
"locale": "en-US",
"timezone": "UTC",
"screenWidth": 412,
"screenHeight": 915,
"networkType": "wifi"
},
"startedAt": '$(date +%s)000'
}'A 201 Created response with {"ok":true} confirms the key is valid and data was accepted. The session will appear in your dashboard within a few seconds.