Docs
Errors

Report errors from your backend

The same error pipeline that powers the JS recorder accepts errors from any backend or native client. Authenticate with your site's API key, POST a small JSON payload, and the error shows up in your dashboard alongside browser errors — tagged backend so you can filter frontend and server-side issues separately.

Endpoint

POST https://api.nevision.app/public/errors/ingest
Content-Type: application/json
x-nevision-api-key: YOUR_API_KEY

Find siteId and the API key in your dashboard under Sites → [your site] → Install. The API key is only required for server-to-server calls — never embed it in client-side code that ships to a browser.

Payload

{
  "siteId": "YOUR_SITE_ID",
  "errors": [
    {
      "type": "uncaught",
      "message": "NullPointerException in /checkout",
      "stack": "...",
      "fingerprint": "checkout-npe-v1",
      "timestamp": 1719000000000,
      "url": "https://api.example.com/checkout",
      "filename": "CheckoutController.java",
      "lineno": 142
    }
  ]
}

fingerprint is how errors get grouped — pick a stable value (exception class + route works well). timestamp is Unix epoch milliseconds. Up to 20 errors per request.

Python

import time, requests

def report_error(exc: Exception, route: str):
    requests.post(
        "https://api.nevision.app/public/errors/ingest",
        headers={"x-nevision-api-key": "YOUR_API_KEY"},
        json={
            "siteId": "YOUR_SITE_ID",
            "errors": [{
                "type": "uncaught",
                "message": str(exc),
                "stack": "".join(__import__("traceback").format_exception(exc)),
                "fingerprint": f"{type(exc).__name__}:{route}",
                "timestamp": int(time.time() * 1000),
                "url": route,
            }],
        },
        timeout=5,
    )

try:
    do_work()
except Exception as e:
    report_error(e, "/checkout")
    raise

PHP

<?php
function nevision_report(Throwable $e, string $route): void {
    $payload = json_encode([
        "siteId" => "YOUR_SITE_ID",
        "errors" => [[
            "type" => "uncaught",
            "message" => $e->getMessage(),
            "stack" => $e->getTraceAsString(),
            "fingerprint" => get_class($e) . ":" . $route,
            "timestamp" => (int) (microtime(true) * 1000),
            "url" => $route,
            "filename" => $e->getFile(),
            "lineno" => $e->getLine(),
        ]],
    ]);

    $ch = curl_init("https://api.nevision.app/public/errors/ingest");
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_HTTPHEADER => [
            "Content-Type: application/json",
            "x-nevision-api-key: YOUR_API_KEY",
        ],
        CURLOPT_TIMEOUT => 5,
        CURLOPT_RETURNTRANSFER => true,
    ]);
    curl_exec($ch);
    curl_close($ch);
}

.NET (C#)

using System.Net.Http;
using System.Net.Http.Json;

public static class Nevision
{
    private static readonly HttpClient Http = new() {
        BaseAddress = new Uri("https://api.nevision.app/")
    };

    public static async Task ReportAsync(Exception ex, string route)
    {
        var req = new HttpRequestMessage(HttpMethod.Post, "public/errors/ingest")
        {
            Content = JsonContent.Create(new {
                siteId = "YOUR_SITE_ID",
                errors = new[] {
                    new {
                        type = "uncaught",
                        message = ex.Message,
                        stack = ex.ToString(),
                        fingerprint = $"{ex.GetType().Name}:{route}",
                        timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
                        url = route,
                    }
                }
            })
        };
        req.Headers.Add("x-nevision-api-key", "YOUR_API_KEY");
        await Http.SendAsync(req);
    }
}

Java

import java.net.URI;
import java.net.http.*;
import java.io.StringWriter;
import java.io.PrintWriter;

public class Nevision {
    private static final HttpClient CLIENT = HttpClient.newHttpClient();

    public static void report(Throwable t, String route) throws Exception {
        StringWriter sw = new StringWriter();
        t.printStackTrace(new PrintWriter(sw));
        String fingerprint = t.getClass().getSimpleName() + ":" + route;

        String body = """
            {"siteId":"YOUR_SITE_ID","errors":[{
              "type":"uncaught",
              "message":%s,
              "stack":%s,
              "fingerprint":%s,
              "timestamp":%d,
              "url":%s
            }]}
            """.formatted(
                quote(t.getMessage()),
                quote(sw.toString()),
                quote(fingerprint),
                System.currentTimeMillis(),
                quote(route)
            );

        HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create("https://api.nevision.app/public/errors/ingest"))
            .header("Content-Type", "application/json")
            .header("x-nevision-api-key", "YOUR_API_KEY")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        CLIENT.sendAsync(req, HttpResponse.BodyHandlers.discarding());
    }

    private static String quote(String s) {
        if (s == null) return "null";
        return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
    }
}

Kotlin (Android / JVM)

import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject

object Nevision {
    private val client = OkHttpClient()
    private val JSON = "application/json".toMediaType()

    fun report(t: Throwable, route: String) {
        val payload = JSONObject().apply {
            put("siteId", "YOUR_SITE_ID")
            put("errors", JSONArray().put(JSONObject().apply {
                put("type", "uncaught")
                put("message", t.message ?: t.javaClass.simpleName)
                put("stack", t.stackTraceToString())
                put("fingerprint", "${t.javaClass.simpleName}:$route")
                put("timestamp", System.currentTimeMillis())
                put("url", route)
            }))
        }

        val req = Request.Builder()
            .url("https://api.nevision.app/public/errors/ingest")
            .header("x-nevision-api-key", "YOUR_API_KEY")
            .post(payload.toString().toRequestBody(JSON))
            .build()

        client.newCall(req).enqueue(object : Callback {
            override fun onFailure(call: Call, e: java.io.IOException) {}
            override fun onResponse(call: Call, response: Response) { response.close() }
        })
    }
}

iOS (Swift)

import Foundation

enum Nevision {
    static func report(_ error: Error, route: String) {
        let url = URL(string: "https://api.nevision.app/public/errors/ingest")!
        var req = URLRequest(url: url)
        req.httpMethod = "POST"
        req.setValue("application/json", forHTTPHeaderField: "Content-Type")
        req.setValue("YOUR_API_KEY", forHTTPHeaderField: "x-nevision-api-key")

        let ns = error as NSError
        let payload: [String: Any] = [
            "siteId": "YOUR_SITE_ID",
            "errors": [[
                "type": "uncaught",
                "message": error.localizedDescription,
                "stack": "\(ns.domain) \(ns.code)\n\(Thread.callStackSymbols.joined(separator: \"\\n\"))",
                "fingerprint": "\(ns.domain):\(route)",
                "timestamp": Int(Date().timeIntervalSince1970 * 1000),
                "url": route,
            ]],
        ]
        req.httpBody = try? JSONSerialization.data(withJSONObject: payload)

        URLSession.shared.dataTask(with: req).resume()
    }
}

Response

{ "accepted": 1 }

The endpoint always returns 200 — check the accepted count to verify ingestion. If it's 0, the response includes an error field (Invalid site, Errors disabled, or limitReached: true when you've hit your monthly plan quota).

Tips

  • Pick stable fingerprints. ExceptionClass:route groups well; including the message tends to over-split.
  • Fire and forget. Don't block your request path waiting for the response — use a short timeout (5s) or a background queue.
  • Batch when you can. Up to 20 errors per request reduces overhead in worker/cron contexts.
  • Don't ship the API key to browsers. Server-side only.