Crash Reporting

Real-time error monitoring and crash reporting via REST API.

Overview

The Crash Reporting tool helps you monitor errors and crashes in your applications. Submit crashes via our REST API from any platform or language.

Features

  • Real-time crash monitoring
  • Smart crash grouping by stack trace
  • Session tracking and crash-free metrics
  • Custom events and breadcrumbs
  • File attachments (screenshots, logs)
  • Email and webhook alerts
  • Configurable data retention

Getting Started

  1. Open your project in SureProgramming
  2. In the Tools section, enable "Crashes"
  3. Go to Settings > API Keys and copy your SDK key
  4. Integrate the REST API into your application

API Base URL

https://sureprogramming.com/api/crashes

Authentication

Include your SDK key in the Authorization header:

Authorization: Bearer sp_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

Submit a Crash

POST to /api/crashes/submit

Request Body

{
    "exception_type": "NullPointerException",
    "message": "Cannot read property 'x' of null",
    "stack_trace": "Error: Cannot read property 'x' of null\n    at MyClass.method (app.js:42:15)\n    at main (app.js:10:5)",
    "app_version": "1.2.3",
    "environment": "production",
    "os_name": "iOS",
    "os_version": "17.0",
    "device_model": "iPhone 15",
    "user_identifier": "user_123",
    "session_id": "sess_abc123",
    "breadcrumbs": [
        {"type": "navigation", "message": "Opened settings", "timestamp": "2026-09-14T10:30:00Z"},
        {"type": "user", "message": "Tapped save button", "timestamp": "2026-09-14T10:30:05Z"}
    ],
    "custom_data": {
        "subscription_tier": "premium",
        "feature_flags": {"dark_mode": true}
    }
}

Response

{
    "success": true,
    "data": {
        "crash_id": 12345,
        "issue_id": 67,
        "is_new_issue": false,
        "attachments": []
    }
}

With Attachments

Use multipart/form-data to include files:

curl -X POST https://sureprogramming.com/api/crashes/submit \
    -H "Authorization: Bearer YOUR_SDK_KEY" \
    -F "exception_type=Error" \
    -F "message=App crashed" \
    -F "stack_trace=..." \
    -F "attachments[]=@screenshot.png" \
    -F "attachments[]=@debug.log"

Sessions

Track user sessions to calculate crash-free rates.

Start Session

POST to /api/crashes/sessions/start

{
    "session_id": "sess_unique_id",
    "app_version": "1.2.3",
    "os_name": "Android",
    "os_version": "14",
    "environment": "production",
    "user_identifier": "user_123"
}

End Session

POST to /api/crashes/sessions/end

{
    "session_id": "sess_unique_id"
}

Events

Track non-fatal events, warnings, and custom telemetry.

POST to /api/crashes/events

{
    "name": "payment_failed",
    "category": "billing",
    "level": "warning",
    "message": "Card declined",
    "session_id": "sess_abc123",
    "properties": {
        "payment_method": "credit_card",
        "error_code": "insufficient_funds"
    }
}

Event levels: debug, info, warning, error

Platform Examples

JavaScript (Browser)

<script>
window.SureProgrammingConfig = {
    sdkKey: 'YOUR_SDK_KEY',
    appVersion: '1.0.0',
    environment: 'production'
};
</script>
<script src="https://sureprogramming.com/assets/js/crash-reporter.js"></script>

<script>
// Errors are captured automatically
// Manual reporting:
SureProgramming.reportError(new Error('Something went wrong'), {
    userId: 'user_123',
    context: 'checkout_flow'
});

// Set user
SureProgramming.setUser('user_123');

// Add breadcrumb
SureProgramming.addBreadcrumb('user', 'Clicked checkout');
</script>

Swift (iOS)

This is a minimal, working REST transport — it has no retry queue, offline persistence, or native crash capture. Adapt it or copy the .NET example's approach into your own crash-handling code.

import UIKit

class CrashReporter {
    static let shared = CrashReporter(apiKey: "YOUR_SDK_KEY")
    private let apiKey: String
    private let baseURL = URL(string: "https://sureprogramming.com/api/crashes")!
    private let sessionId = UUID().uuidString

    init(apiKey: String) {
        self.apiKey = apiKey
        startSession()
    }

    func report(_ error: Error, customData: [String: Any]? = nil) {
        post(path: "submit", body: [
            "exception_type": String(describing: type(of: error)),
            "message": error.localizedDescription,
            "stack_trace": Thread.callStackSymbols.joined(separator: "\n"),
            "session_id": sessionId,
            "custom_data": customData ?? [:]
        ])
    }

    private func startSession() {
        post(path: "sessions/start", body: [
            "session_id": sessionId,
            "os_name": "iOS",
            "os_version": UIDevice.current.systemVersion
        ])
    }

    private func post(path: String, body: [String: Any]) {
        var request = URLRequest(url: baseURL.appendingPathComponent(path))
        request.httpMethod = "POST"
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try? JSONSerialization.data(withJSONObject: body)

        URLSession.shared.dataTask(with: request) { _, response, error in
            if let error = error {
                print("Crash report submission failed: \(error)")
                return
            }
            if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
                print("Crash report rejected with status \(http.statusCode)")
            }
        }.resume()
    }
}

Kotlin (Android)

Uses HttpURLConnection so the snippet has no extra dependency; swap in OkHttp/Retrofit if your app already uses one. Run post() off the main thread (e.g. from a background dispatcher) — it blocks on network I/O as written.

class CrashReporter(private val apiKey: String) {
    private val baseUrl = "https://sureprogramming.com/api/crashes"
    private val sessionId: String = UUID.randomUUID().toString()

    init {
        startSession()
        Thread.setDefaultUncaughtExceptionHandler { _, throwable ->
            reportCrash(throwable)
        }
    }

    fun reportCrash(throwable: Throwable, customData: Map<String, Any>? = null) {
        val payload = JSONObject().apply {
            put("exception_type", throwable.javaClass.name)
            put("message", throwable.message ?: "Unknown error")
            put("stack_trace", Log.getStackTraceString(throwable))
            put("session_id", sessionId)
            put("custom_data", JSONObject(customData ?: emptyMap<String, Any>()))
        }
        post("/submit", payload)
    }

    private fun startSession() {
        val payload = JSONObject().apply {
            put("session_id", sessionId)
            put("os_name", "Android")
            put("os_version", Build.VERSION.RELEASE)
        }
        post("/sessions/start", payload)
    }

    private fun post(path: String, payload: JSONObject) {
        val connection = URL(baseUrl + path).openConnection() as HttpURLConnection
        connection.requestMethod = "POST"
        connection.doOutput = true
        connection.setRequestProperty("Authorization", "Bearer $apiKey")
        connection.setRequestProperty("Content-Type", "application/json")

        connection.outputStream.use { it.write(payload.toString().toByteArray()) }

        val status = connection.responseCode
        if (status !in 200..299) {
            Log.w("CrashReporter", "Crash report rejected with status $status")
        }
        connection.disconnect()
    }
}

C# (.NET)

See the downloadable CrashReportingExample.csproj for a fuller, compiled/tested version of this. Notably it builds the request body as buffered StringContent rather than PostAsJsonAsync — in some hosting/runtime combinations, PostAsJsonAsync negotiates chunked transfer encoding with no Content-Length header, which this API does not parse (the request is treated as empty and every field is rejected as missing, even though the JSON was fully populated).

public class CrashReporter
{
    private readonly HttpClient _client;
    private readonly string _sessionId = Guid.NewGuid().ToString();

    public CrashReporter(string apiKey)
    {
        _client = new HttpClient();
        _client.BaseAddress = new Uri("https://sureprogramming.com/api/crashes/");
        _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");

        StartSession();
        AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
    }

    public async Task<bool> ReportAsync(Exception ex, Dictionary<string, object>? customData = null)
    {
        var payload = new
        {
            exception_type = ex.GetType().Name,
            message = ex.Message,
            stack_trace = ex.StackTrace,
            session_id = _sessionId,
            custom_data = customData
        };

        return await PostAsync("submit", payload);
    }

    private void StartSession()
    {
        _ = PostAsync("sessions/start", new
        {
            session_id = _sessionId,
            os_name = Environment.OSVersion.Platform.ToString(),
            os_version = Environment.OSVersion.VersionString
        });
    }

    // Buffered StringContent, not PostAsJsonAsync — see note above.
    private async Task<bool> PostAsync(string path, object payload)
    {
        using var content = new StringContent(
            JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

        var response = await _client.PostAsync(path, content);
        if (!response.IsSuccessStatusCode)
        {
            Console.Error.WriteLine($"Crash report rejected with status {(int)response.StatusCode}");
        }
        return response.IsSuccessStatusCode;
    }

    private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (e.ExceptionObject is Exception ex)
            ReportAsync(ex).Wait();
    }
}

Python

import requests
import traceback
import uuid
import sys

class CrashReporter:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = "https://sureprogramming.com/api/crashes"
        self.session_id = str(uuid.uuid4())
        self._start_session()
        sys.excepthook = self._handle_exception

    def report(self, exception, custom_data=None):
        payload = {
            "exception_type": type(exception).__name__,
            "message": str(exception),
            "stack_trace": traceback.format_exc(),
            "session_id": self.session_id,
            "custom_data": custom_data or {}
        }
        self._post("/submit", payload)

    def _start_session(self):
        import platform
        self._post("/sessions/start", {
            "session_id": self.session_id,
            "os_name": platform.system(),
            "os_version": platform.release()
        })

    def _handle_exception(self, exc_type, exc_value, exc_tb):
        self.report(exc_value)
        sys.__excepthook__(exc_type, exc_value, exc_tb)

    def _post(self, path, payload):
        response = requests.post(
            f"{self.base_url}{path}",
            json=payload,
            headers={"Authorization": f"Bearer {self.api_key}"}
        )
        if not response.ok:
            print(f"Crash report rejected with status {response.status_code}")

Alerts & Integrations

Configure email alerts and webhook integrations in your project settings:

  • Email alerts - Get notified of new crashes, regressions, and spikes
  • Slack - Post crash notifications to a channel
  • Discord - Send alerts to Discord webhooks
  • Custom webhooks - Integrate with any service

Best Practices

  • Start sessions early - Call session start at app launch for accurate crash-free metrics
  • Include context - Add user IDs, breadcrumbs, and custom data to help debug issues
  • Use environments - Separate development, staging, and production crashes
  • Handle offline - Queue crashes locally and submit when connectivity returns