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")
raisePHP
<?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:routegroups 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.