API Reference

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.

Base URL https://api.performanceinsights.ai Format JSON over HTTPS Auth Bearer API key

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.

Scope

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.

POST/api/v1/customers/me/api-keys

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.

GET/api/v1/customers/me/api-keys

Your named keys, newest first, revoked ones included. Same shape as above, without api_key.

DELETE/api/v1/customers/me/api-keys/{id}

Revokes a named key. It stops working at once. Returns 204.

POST/api/v1/customers/me/api-key/rotate

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.

TierMonthly dumpsLargest dumpMonthly upload volumeEngineer reviewWho it's for
solo12 GiB2 GiBNoAn individual developer debugging their own crashes.
isv108 GiB40 GiBYesA software vendor piping customer crash dumps through automated triage.
enterprise2516 GiB100 GiBYesAn 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

POST/api/v1/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.

POST/api/v1/signup/verify

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

POST/api/v1/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.

GET/api/v1/dumps

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.

GET/api/v1/dumps/{id}

Fetch one dump's status. Same shape as the upload response.

GET/api/v1/dumps/download?uri=&token=

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.

DELETE/api/v1/dumps/{id}

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

GET/api/v1/dumps/{id}/report

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

POST/api/v1/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_name is the PDB file name (acme.pdb), module_hash its GUID+age.
  • ELF executable or shared object, for Linux core dumps: module_name is the file name the process ran as (acmed, libacme.so.1), module_hash its GNU build ID (readelf -n). When the file carries a build ID we read it ourselves, and an upload whose module_hash doesn'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 .o files 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).

GET/api/v1/symbols

Your uploaded symbols, same shape as above.

DELETE/api/v1/symbols/{id}

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

GET/api/v1/customers/me

Returns {contact, email} for the account.

GET/api/v1/customers/me/export

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.

PATCH/api/v1/customers/me/retention

Body: {retention_preference_days} — how long uploaded dumps are retained, at most 90 days (422 above that). Returns the value set.

PUT/api/v1/customers/me/kb-sharing

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}.

PUT/api/v1/customers/me/data-region

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

POST/api/v1/customers/me/webhook

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

StatusMeaning
401Missing, malformed, or invalid API key.
403Valid key, wrong permissions: an upload-scoped key on anything but uploading, or a staff-only route.
404No such dump/report/customer, or it belongs to a different account.
409Signup email already registered and verified; deleting a dump while it's being analysed; a 21st active named key.
422Malformed request body, or an unrecognized dump format.
429Monthly quota exceeded, or a signup/verification resend was attempted too soon.
503Temporarily overloaded. Retry after the number of seconds in the Retry-After header.