error.page docs

Report errors from any app

The Ingest API for backend services, mobile, desktop and CLI apps — send an error with logs, screenshots and core/crash dumps over plain HTTPS. Copy-paste examples in curl, Python, Node, Go, Ruby, PHP and Java.

This is the endpoint non-web applications use to report an error. It takes one error event — a message, structured context, and optionally a screenshot and diagnostic files (logs, stack traces, core/crash dumps, heap dumps, HAR captures) — over a single HTTPS request. Read API overview & authentication first for base URL, keys and limits.

POST https://error.page/api/v2/ingest

Authenticate with your project key and secret key (server-to-server), or a signed token for high-assurance ingest:

X-Project-Key: <api_key>
X-Secret-Key:  <secret_key>

Content types

  • application/json — for a plain report with no files. Simplest for backends.
  • multipart/form-data — required whenever you attach a screenshot or files. Send the fields below as form parts, files as file parts, and metadata as a JSON string in a metadata part.

Fields

Field Type Required Notes
error_type string One of network, js_crash, server_error, api_failure, other. Backends/CLIs usually use server_error, api_failure, or other.
message string The human-readable error (≤ 5,000 chars). Put your exception message here.
title string Short label (≤ 255). Falls back to Unhandled error if message is empty.
release string Build/version that produced the error (≤ 120), e.g. api@2026.7.4. Also read from metadata.release / metadata.appVersion. Ties the event to a release.
endpoint string The route, command or operation that failed (≤ 500), e.g. POST /v1/checkout or cron:nightly-sync. Used for grouping.
status_code integer HTTP-style status, 400–599, when applicable.
user_context string A stable identifier for the affected user/session (hashed server-side) so "users affected" counts are distinct. Avoid raw PII.
metadata object Arbitrary structured context: host, environment, stack trace, request id, etc. Sent as a JSON object (JSON body) or a JSON string (multipart).
image file A single screenshot (jpg, jpeg, png, gif, webp; ≤ 10 MB). Becomes the incident's screenshot preview.
attachments[] file[] Up to 10 diagnostic files (see allowed types below; ≤ 50 MB each). Logs, dumps, core files.

Allowed attachment types (anything executable/scriptable is refused): log, txt, json, csv, xml, yaml, yml, md, ini, conf, trace, stacktrace, har, png, jpg, jpeg, gif, webp, pdf, zip, gz, tgz, tar, dmp, core, hprof.

Size caps are per report, and count text + files together, bounded by your plan (Free: text only, 3 KB; Pro: 1 MB; Business: 2 MB; Enterprise: unlimited). If a report is too big, send a trimmed log or gzip it (.gz/.zip are allowed).

Success response — 201 Created

{
  "message": "Error event ingested",
  "event_id": 12345,
  "project_slug": "my-service"
}

Minimal report (JSON)

The smallest useful call — no files:

curl -sS https://error.page/api/v2/ingest \
  -H "X-Project-Key: $ERRORPAGE_KEY" \
  -H "X-Secret-Key: $ERRORPAGE_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
        "error_type": "server_error",
        "message": "NullReferenceException in OrderProcessor.finalize()",
        "endpoint": "POST /v1/checkout",
        "status_code": 500,
        "release": "api@2026.7.4",
        "metadata": { "host": "api-prod-3", "env": "production", "request_id": "req_9f2c" }
      }'

Report with logs, a screenshot and a core dump (multipart)

This is the pattern for "attach the error log, a screenshot, and the crash/core file":

curl -sS https://error.page/api/v2/ingest \
  -H "X-Project-Key: $ERRORPAGE_KEY" \
  -H "X-Secret-Key: $ERRORPAGE_SECRET" \
  -F "error_type=server_error" \
  -F "message=Segfault in image transcoder" \
  -F "endpoint=worker:transcode" \
  -F "release=worker@2026.7.4" \
  -F 'metadata={"host":"worker-12","signal":"SIGSEGV"};type=application/json' \
  -F "image=@/tmp/last-frame.png" \
  -F "attachments[]=@/var/log/worker/transcode.log" \
  -F "attachments[]=@/var/crash/core.34112.gz" \
  -F "attachments[]=@/tmp/stacktrace.txt"

image is the visual preview; attachments[] (repeat the field per file) holds the log, the gzipped core dump, and the stack trace. A Java service would attach a .hprof heap dump; a Windows app a .dmp minidump — all allowed.

Language examples

Python

import os, json, requests

files = {
    "image": ("screenshot.png", open("screenshot.png", "rb"), "image/png"),
    "attachments[]": ("app.log", open("app.log", "rb"), "text/plain"),
}
data = {
    "error_type": "server_error",
    "message": "Timeout calling payments provider",
    "endpoint": "POST /v1/charge",
    "status_code": 504,
    "release": "billing@2026.7.4",
    "metadata": json.dumps({"host": os.uname().nodename, "env": "production"}),
}
r = requests.post(
    "https://error.page/api/v2/ingest",
    headers={"X-Project-Key": os.environ["ERRORPAGE_KEY"],
             "X-Secret-Key": os.environ["ERRORPAGE_SECRET"]},
    data=data, files=files, timeout=10,
)
r.raise_for_status()
print(r.json()["event_id"])

Node.js (fetch + FormData, Node 18+)

import fs from "node:fs";

const form = new FormData();
form.set("error_type", "api_failure");
form.set("message", err.message);
form.set("endpoint", "GET /v1/orders");
form.set("release", process.env.APP_VERSION);
form.set("metadata", JSON.stringify({ host: os.hostname(), env: "production" }));
form.set("attachments[]", new Blob([fs.readFileSync("app.log")]), "app.log");

const res = await fetch("https://error.page/api/v2/ingest", {
  method: "POST",
  headers: {
    "X-Project-Key": process.env.ERRORPAGE_KEY,
    "X-Secret-Key": process.env.ERRORPAGE_SECRET,
  },
  body: form,
});
if (!res.ok) throw new Error(`ingest failed: ${res.status}`);

Go

body, _ := json.Marshal(map[string]any{
    "error_type": "server_error",
    "message":    err.Error(),
    "endpoint":   "grpc/OrderService.Create",
    "release":    version,
    "metadata":   map[string]any{"host": host, "env": "production"},
})
req, _ := http.NewRequest("POST", "https://error.page/api/v2/ingest", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Project-Key", os.Getenv("ERRORPAGE_KEY"))
req.Header.Set("X-Secret-Key", os.Getenv("ERRORPAGE_SECRET"))
resp, err := http.DefaultClient.Do(req)

Ruby

require "net/http"; require "json"
uri = URI("https://error.page/api/v2/ingest")
req = Net::HTTP::Post.new(uri, {
  "X-Project-Key" => ENV["ERRORPAGE_KEY"],
  "X-Secret-Key"  => ENV["ERRORPAGE_SECRET"],
  "Content-Type"  => "application/json",
})
req.body = { error_type: "server_error", message: e.message,
             endpoint: "sidekiq:MailerJob", release: APP_VERSION,
             metadata: { host: Socket.gethostname, env: "production" } }.to_json
Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }

PHP

$ch = curl_init("https://error.page/api/v2/ingest");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "X-Project-Key: " . getenv("ERRORPAGE_KEY"),
        "X-Secret-Key: "  . getenv("ERRORPAGE_SECRET"),
    ],
    CURLOPT_POSTFIELDS => [
        "error_type" => "server_error",
        "message"    => $e->getMessage(),
        "endpoint"   => "artisan schedule:run",
        "release"    => APP_VERSION,
        "metadata"   => json_encode(["host" => gethostname(), "env" => "production"]),
        "attachments[]" => new CURLFile("/var/log/app.log"),
    ],
]);
curl_exec($ch);

Java (java.net.http)

String body = """
  {"error_type":"server_error","message":"%s","endpoint":"POST /orders",
   "release":"%s","metadata":{"host":"%s","env":"production"}}
  """.formatted(ex.getMessage(), version, host);

HttpRequest req = HttpRequest.newBuilder(URI.create("https://error.page/api/v2/ingest"))
    .header("X-Project-Key", System.getenv("ERRORPAGE_KEY"))
    .header("X-Secret-Key",  System.getenv("ERRORPAGE_SECRET"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());

Patterns for different app types

  • Backend service / API: report in your global exception handler or middleware. Put the exception message in message, the route in endpoint, the HTTP status in status_code, and request id / environment / user id (hashed) in metadata. Attach the request log if it helps triage.
  • Scheduled job / daemon: use error_type: other, set endpoint to the job name (cron:nightly-sync), and attach the run log.
  • Mobile / desktop app: report from a lightweight backend or a trusted client build. Attach a screenshot (image) and a redacted log.
  • CLI tool: on unexpected exit, POST a report with --verbose output as an attachment so you can reproduce.

Reliability guidance

  • Report out-of-band. Never let a failed ingest call break the app: fire it after you've handled the user-facing error, with a short timeout (5–10s), and swallow any error from the call itself.
  • Retry only safe failures. Retry on network errors and 5xx with exponential backoff + jitter. Do not retry a 201 (it succeeded) or a 4xx (it won't).
  • Respect back-pressure. On 429, honour Retry-After; if you're hammering the endpoint, sample or aggregate before sending.
  • Keep payloads lean. Trim/rotate logs to the relevant window and gzip large files so you stay under your plan's per-report cap.