AI-powered crash analysis is now available on all plans — including Free.Read the crash analysis guide

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.

Using an SDK? The Flutter and React Native SDKs call these endpoints automatically — you do not need to integrate directly unless you are building a custom client or testing your integration with curl.

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_here

Requests 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.com

All endpoints are prefixed with /ingest.

Sessions

POST/ingest/session

Start 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).

POST/ingest/session/end

Mark 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.

This endpoint returns 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

POST/ingest/events

Send 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
}
Network events sent via /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

POST/ingest/crash

Report 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

POST/ingest/network

Send 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

EndpointMax items per requestRecommended flush interval
/ingest/events500 events5 seconds (SDK default)
/ingest/network200 requestsFlush at session end or every 30 s

Rate limits

Route prefixLimitWindow
/ingest/*600 requestsper API key / per minute
/api/auth/*20 requestsper 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
}
StatusMeaningRetry?
200Session end accepted (idempotent)No
201Data created and acceptedNo
400Malformed JSON or missing required fieldsNo — fix payload
401Missing or invalid API keyNo — check key
404Session not found or belongs to another projectNo — check sessionId
422Schema validation failed — see error.detailsNo — fix payload
429Rate limitedYes — exponential backoff
500Server errorYes — 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.