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, andmetadataas a JSON string in ametadatapart.
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/.zipare 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 inendpoint, the HTTP status instatus_code, and request id / environment / user id (hashed) inmetadata. Attach the request log if it helps triage. - Scheduled job / daemon: use
error_type: other, setendpointto 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
--verboseoutput 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
5xxwith exponential backoff + jitter. Do not retry a201(it succeeded) or a4xx(it won't). - Respect back-pressure. On
429, honourRetry-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.