DumpTriage API — v1
Upload a Windows or Linux crash dump, get an AI-triaged report back — automatically, or, on the ISV and Enterprise tiers, escalated to a human engineer when the evidence calls for it. Built for enterprise IT, ISVs shipping crash-reporting integrations, and solo developers debugging one dump at a time.
1. Overview
Every dump goes through the same pipeline: upload → Tier 1 (fast automated triage) → Tier 2 (deep automated review, if Tier 1 isn't confident) → Tier 3 (human engineer, if the evidence still doesn't add up). The solo tier has no Tier 3: a dump that would go to an engineer gets its automated report, with delivered_without_review: true on the report. Whether a dump was settled automatically or by an engineer is recorded in the report's decision field. In the pilot most cases have gone to an engineer; we'll publish the automated share once we've measured it.
V1 analyzes Windows user-mode (minidump) and kernel-mode (full/kernel memory) dumps. It also analyzes Linux process core dumps and kernel vmcores (kdump). Linux is in early access. A clear-cut Linux report is delivered like a Windows one; send human_review=true on upload to have an engineer review it first.
2. Authentication
Every request except signup and verification needs a bearer API key:
Authorization: Bearer dts_<your-key>
Your account key is returned once, by signup (§5). Store it immediately: only a hash is kept, so it can't be shown again. The account key has full access to the account.
You can also mint up to 20 named keys, each with a scope. An upload key can only call POST /api/v1/dumps, and every other route answers it with 403. That's the key to install on machines that send dumps, such as the Windows and Linux agents in dumptriage-tools (open source, MIT, with the scripts that turn on dump capture): a leaked upload key can't read reports or change the account. A full key can do everything the account key can.
Body: {name, scope}. name is 1–64 characters, and scope is "upload" (the default) or "full". Returns 201 with {id, name, scope, created_at, revoked_at, api_key}. The api_key is shown only this once. Returns 409 when 20 named keys are already active.
Your named keys, newest first, revoked ones included. Same shape as above, without api_key.
Revokes a named key. It stops working at once. Returns 204.
Replaces the account key and returns {api_key}. The key used for this request stops working immediately. This is also how to revoke a leaked account key. Named keys aren't affected.
3. Tiers & Quotas
Self-serve accounts get a free, metered quota per rolling 30-day period. Until you've clicked the link in the verification email, the account can upload a single dump of up to 256 MiB; verifying lifts it to your tier's limits below. Uploading past your quota returns 429 until the period rolls over. There's no paid tier yet — if you need a higher limit, get in touch.
| Tier | Monthly dumps | Largest dump | Monthly upload volume | Engineer review | Who it's for |
|---|---|---|---|---|---|
solo | 1 | 2 GiB | 2 GiB | No | An individual developer debugging their own crashes. |
isv | 10 | 8 GiB | 40 GiB | Yes | A software vendor piping customer crash dumps through automated triage. |
enterprise | 25 | 16 GiB | 100 GiB | Yes | An internal IT/engineering team triaging a fleet's worth of dumps. |
The tier you sign up for decides engineer review during the pilot too: a solo account never has a dump escalated to an engineer.
Re-uploading a dump you've already uploaded (identical bytes) is deduplicated and doesn't count against your quota. It returns the existing dump unchanged, including its original human_review choice.
4. Quickstart
# 1. Sign up (choose tier: solo | isv | enterprise)
curl -X POST https://api.performanceinsights.ai/api/v1/signup \
-H "Content-Type: application/json" \
-d '{"org":"Acme Inc","contact":"Jane Dev","email":"jane@acme.example","tier":"solo","accept_terms":true}'
# -> {"customer_id": "...", "status": "pending_verification", "api_key": "dts_..."}
# save the key now: it's shown once
# 2. Click the link in the verification email. The key works before that,
# for one dump of up to 256 MiB; verifying lifts you to your tier's quota.
# 3. Upload a dump
curl -X POST https://api.performanceinsights.ai/api/v1/dumps \
-H "Authorization: Bearer dts_..." \
-F "file=@crash.dmp"
# -> {"id": "...", "dump_type": "user_mode", "status": "uploaded", "job_state": "queued",
# "size_bytes": 123456, "uploaded_at": "...", "download_url": "/api/v1/dumps/download?uri=...&token=..."}
# 4. Poll for the report
curl https://api.performanceinsights.ai/api/v1/dumps/<id>/report \
-H "Authorization: Bearer dts_..."
# -> {"job_state": "delivered", "decision": "delivered", "reports": [...]}
5. Signup
Body: {org, contact, email, tier, accept_terms} — accept_terms must be true (422 otherwise): it records your acceptance of the Terms of Service and Privacy Policy, including the pilot rule that you upload only synthetic or sample data, never real customer data. tier is one of solo, isv, enterprise. org and contact are at most 200 characters; control characters are stripped and runs of whitespace collapsed. Returns 202 with {customer_id, status: "pending_verification", api_key} and sends a verification email. The api_key works straight away, within the unverified limit (§3), and is the only time it's shown. Posting again with the same (unverified) email only resends the link, at most once per 60 seconds (429 otherwise): it returns api_key: null, the key from the first signup keeps working, and org, contact and tier aren't changed. An email that's already verified returns 409. Also rate-limited per source IP (a handful of signups per hour) — a 429 past that limit carries a Retry-After header.
Body: {token} (from the emailed link). Marks the email verified, which lifts the account to its tier's quota. Returns {customer_id, api_key}, where api_key is normally null: the key from signup keeps working and isn't replaced. An invalid, already-used, or expired (24h) token returns 400. The emailed link opens a confirmation page; the key is only issued once you press its button, so a mail scanner fetching the link can't use up your token.
6. Dumps
Multipart upload, field name file, with two optional fields. human_review=true asks for an engineer to review the dump, so its report is never auto-delivered; on the solo tier, which has no engineer review, it is refused with 403 human_review_not_included (GET /api/v1/customers/me says human_review_included). hostname is the machine the dump came from; only a keyed hash of it is stored, never the name itself. Works with an upload-scoped key (§2). The dump type is detected from the file itself, not a parameter, and returned as dump_type: MDMP is a Windows minidump (user_mode); PAGE is a Windows kernel-mode memory dump: small (kernel mini), kernel, complete or automatic (kernel_mode); an ELF core file is a Linux process core (linux_user_mode), unless it's a kernel vmcore; a vmcore, or a KDUMP, DISKDUMP or makedumpfile flattened file, is a Linux kernel dump (linux_kernel_mode). Anything else, or a minidump whose header is broken, is the 422 below. Returns 201 with the dump record: {id, dump_type, status, size_bytes, uploaded_at, job_state, download_url, human_review_requested}. 422 for an unrecognized format, 413 if the file is larger than your tier's per-dump size (or what's left of your monthly upload volume), or 429 if you're over quota (§3). Oversized uploads are cut off at the limit, not after the full transfer.
Lists your dumps, most recent first. Same shape as the upload response, one per dump. Paged: limit (default 100, at most 500) and offset. The X-Total-Count response header holds how many dumps you have in all.
Fetch one dump's status. Same shape as the upload response.
Downloads the original file via a short-lived (1h) signed URL. Take uri/token straight from the download_url field on the dump record above — don't construct them yourself. An expired or tampered token returns 403.
Deletes a dump now instead of at the end of its retention period. The file and its analysis transcripts are removed, exactly as the retention sweep would remove them. Returns 204. A queued analysis is cancelled. While the dump is being analysed, it returns 409; try again once the report is in.
7. Reports
Returns {dump_id, job_state, decision, reports}. decision is null while still processing, otherwise "delivered" (automated triage resolved it) or "escalated" (a human is on it). reports holds each tier's findings, confidence signals, and cost accounting as they complete.
8. Symbols
Multipart upload (file, module_name, module_hash) of your own symbols. They're isolated per account, never shared across customers, and used to symbolicate your own modules during analysis. The file's format is detected from its contents:
- PDB, for Windows dumps:
module_nameis the PDB file name (acme.pdb),module_hashits GUID+age. - ELF executable or shared object, for Linux core dumps:
module_nameis the file name the process ran as (acmed,libacme.so.1),module_hashits GNU build ID (readelf -n). When the file carries a build ID we read it ourselves, and an upload whosemodule_hashdoesn't match it is refused. Upload the binary with its debug info, or the stripped binary plus its separate debug file, uploaded under the name its debuglink gives (readelf --debug-dump=links, usually<name>.debug). Without them, frames in your code can't be named. You can keep every release uploaded: each core is matched to the exact builds it ran, by build ID. Core files and.ofiles are refused.
Returns 201 with {id, module_name, module_hash, format, build_id, size_bytes, scan_status, uploaded_at}, where format is pdb or elf and build_id is the ELF build ID we read from the file (null for a PDB).
Your uploaded symbols, same shape as above.
Deletes an uploaded symbol file. Returns 204. Symbols don't expire with your dump retention setting, so this is how to remove them.
9. Account
Returns {contact, email} for the account.
A zip of everything the API holds for the account: account.json, dumps.json (the dump list, each with a download link valid for a short time) and reports/<dump id>.json for every analysed dump, in the same shapes the other routes return. Take it before closing the account; fetch the raw dumps from their links straight away, since the links expire.
Body: {retention_preference_days} — how long uploaded dumps are retained, at most 90 days (422 above that). Returns the value set.
Body: {enabled}. Sets whether summaries of your resolved cases may inform other customers' analyses. It's off unless you turn it on, and turning it off applies from the next analysis. Returns {kb_sharing}.
The debugger workers run in the EU at the moment, and there is no US worker. With "us" and strict: true, your dumps wait and are never analysed. With the region unset, the debugger and the AI analysis stay in the EU anyway, where the service runs; "eu" with strict: true makes that a requirement for your account.
Body: {region, strict}. region is "eu", "us" or null (no preference, the default: the service's own region, the EU). It decides where the debugger analyses your raw dumps and, for "eu", sends the AI analysis to Google's EU endpoint. With strict (default true), a dump waits for a worker in that region rather than leave it. If the configured model has no endpoint there, the analysis fails instead of sending the transcript elsewhere. With strict: false, workers in the region are tried first. Applies from the next analysis. Returns {data_region, data_region_strict}, which GET /api/v1/customers/me also shows.
10. Webhooks
Body: {url, secret} — url must be a public http(s) address; secret is 16–256 characters of your choosing. Once registered, your endpoint receives a POST with {dump_id, status} when a dump's report is delivered or its analysis fails, instead of (or alongside) polling §7. Failed deliveries are retried.
Every delivery is signed with your secret so you can verify it came from us:
X-Triage-Timestamp: 1789761600
X-Triage-Signature: sha256=<hex HMAC-SHA256(secret, "<timestamp>.<raw request body>")>
Compute the HMAC over the timestamp, a literal ., and the raw body bytes exactly as received (don't re-serialize the JSON), compare in constant time, and reject timestamps more than five minutes old:
import hashlib, hmac, time
def verify(secret: str, headers, raw_body: bytes) -> bool:
ts = headers["X-Triage-Timestamp"]
if abs(time.time() - int(ts)) > 300:
return False
expected = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, headers["X-Triage-Signature"])
11. Errors
| Status | Meaning |
|---|---|
401 | Missing, malformed, or invalid API key. |
403 | Valid key, wrong permissions: an upload-scoped key on anything but uploading, or a staff-only route. |
404 | No such dump/report/customer, or it belongs to a different account. |
409 | Signup email already registered and verified; deleting a dump while it's being analysed; a 21st active named key. |
422 | Malformed request body, or an unrecognized dump format. |
429 | Monthly quota exceeded, or a signup/verification resend was attempted too soon. |
503 | Temporarily overloaded. Retry after the number of seconds in the Retry-After header. |