Guide: insurance claims
An end-to-end walkthrough of the Attest flow, entirely against the sandbox. Set $PROVENANCE_API and $PRV_KEY from the Quickstart first.
1. Capture & attest
In production, the policyholder captures through the attested SDK, which signs the bundle in the device enclave and submits it to POST /v1/claims/attest. The API recomputes the hash, verifies the signature and cert chain, scores the asset, and stores it. See the attest reference.
In the sandbox this is already done for you — four claims are seeded.
2. Triage the queue
Pull the claims list the way an adjuster's dashboard does:
curl $PROVENANCE_API/v1/claims \
-H "Authorization: Bearer $PRV_KEY"
{
"data": [
{ "claim_id": "CLM-SANDBOX-CLEAN", "overall_status": "verified", "overall_risk_score": 0 },
{ "claim_id": "CLM-SANDBOX-FRAUD", "overall_status": "failed", "overall_risk_score": 100 }
]
}
3. Inspect a suspicious claim
Pull the full provenance for the fraud claim — every asset and its factors:
curl $PROVENANCE_API/v1/claims/CLM-SANDBOX-FRAUD/provenance \
-H "Authorization: Bearer $PRV_KEY"
{ "claim_id": "CLM-SANDBOX-FRAUD", "overall_status": "failed", "overall_risk_score": 100 }
The tampered assets show signature_invalid and sensor_hash_mismatch — the pixels changed after signing. That's risk scoring doing its job.
4. Screen a legacy upload
If an asset is an un-attested web upload (no_manifest), reach for the separate synthetic-media signal — never merged into the risk score:
curl $PROVENANCE_API/v1/media/screen \
-H "Authorization: Bearer $PRV_KEY" \
-H "Content-Type: application/json" \
-d '{ "image_base64": "AAAA" }'
{ "error": { "type": "api_error", "code": "detector_not_configured" } }
When screening isn't configured, the call returns 503 detector_not_configured.
Escalation
When a claim is fraud, escalate it to SIU. POST /v1/claims/:claim_id/escalate appends an escalation chain event to each asset and returns a one-page evidentiary PDF (base64):
// POST /v1/claims/CLM-SANDBOX-FRAUD/escalate { "adjuster_id": "adj_1", "notes": "Tampered manifest." }
// → { "escalation_id": "…", "pdf_base64": "JVBERi0…" }
The PDF carries claim metadata, the risk-factor breakdown for the highest-risk asset, the chain-of-custody timeline, and device info. Scores are advisory — the final decision rests with the adjuster.
Case workflow
The risk score only matters once it's part of someone's job. Every claim has a case with workflow state, so adjusters can triage, assign, and disposition — with a reason code and an append-only audit trail. Cases are carrier-scoped and created lazily (the first time you view the queue or a claim), so there's no backfill.
States move along a fixed graph:
new ──▶ in_review ──▶ approved | denied | escalated
└────────────────▶ approved | denied | escalated
approved | denied ──▶ reopened escalated ──▶ approved | denied | reopened
reopened ──▶ in_review | approved | denied | escalated
Dispositions (approved / denied / escalated) require a reason code — the audit answer to "why was this denied": provenance_verified, risk_accepted, signature_invalid, suspected_fraud, insufficient_evidence, policyholder_withdrew, or other (which additionally requires a note).
# The triage queue, highest-risk first (or ?sort=age, ?status=, ?assignee=).
curl -s "$API/v1/cases?sort=risk" -H "Authorization: Bearer $KEY"
# → { data: [ { case_id, claim_id, status, overall_risk_score, assignee_account_id, … } ], pagination }
# A case with its full history.
curl -s "$API/v1/cases/CLM-SANDBOX-FRAUD" -H "Authorization: Bearer $KEY"
# Assign, then escalate to SIU with a reason (illegal move or missing reason → 400).
curl -s -X POST "$API/v1/cases/CLM-SANDBOX-FRAUD/assign" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"assignee_account_id":"acct_…"}'
curl -s -X POST "$API/v1/cases/CLM-SANDBOX-FRAUD/transition" -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"to_status":"escalated","reason_code":"suspected_fraud","note":"ring match"}'
Every transition is a row in the case's append-only history (who / when / from → to / why) and fires a case.status_changed webhook. In the SDK: prov.cases.list(), prov.cases.get(claimId), prov.cases.assign(claimId, acct), prov.cases.transition(claimId, { toStatus, reasonCode, note }), prov.cases.addNote(claimId, note). Reads need the verify scope; workflow writes need escalate. The dashboard's claim detail shows a Case workflow sidebar with the status, assign-to-me, disposition buttons + reason picker, and the timeline.
Explanations & appeals
A risk score without a plain-English "why" is a bad-denial and regulatory risk. Provenance renders every claim's signals into a deterministic explanation — and gives the policyholder a real appeal path.
Fixed templates, not generative text
GET /v1/claims/:id/explanation returns a summary line, a bullet per risk factor (title, plain-English body, and what would resolve it), and the disposition reason. Every string comes from a fixed, reviewed template table — deliberately not LLM-generated, so explanations are stable, auditable, and defensible. A template_version is stamped on each render, and every render is logged on the case (the compliance trail). The template set is exhaustive by construction: a new risk factor without a template fails the build.
curl -s "$API/v1/claims/CLM-SANDBOX-FRAUD/explanation" -H "Authorization: Bearer $KEY"
# → { template_version, overall_status, summary, factors:[{ code, title, body, resolution_hint }], disposition }
The appeal path
The carrier mints a signed, single-use, 7-day appeal link; the policyholder uses it with no login:
curl -s -X POST "$API/v1/claims/CLM-SANDBOX-FRAUD/appeal-link" -H "Authorization: Bearer $KEY"
# → { appeal_url, token, expires_at }
# The policyholder page reads the explanation, then files:
curl -s -X POST "$API/v1/appeal" -H "Content-Type: application/json" \
-d '{"token":"…","statement":"The damage is real; see the new photos."}'
# → { appeal_id, case_status:"reopened" }
Filing writes an appeals row, reopens the case with an APPEAL_FILED event (so it lands back in the triage queue), and can carry newly attested media. The link is single-use for filing (reusing it → 409) and expires (401). Provenance doesn't adjudicate the appeal — a human does, in the workflow.
Analytics & ROI
Carriers renew on ROI they can see. The Analytics endpoints (enterprise) turn your attestations, factors, screenings, and case dispositions into a carrier-scoped, windowed portfolio view — no warehouse, computed on demand.
curl -s "$API/v1/analytics/summary?from=2026-01-01T00:00:00Z&to=2026-07-01T00:00:00Z" -H "Authorization: Bearer $KEY"
# → { claims:{ total, by_status }, risk:{ by_band, mean_score }, attested_mix, screening,
# dispositions:{ by_status, median_time_to_disposition_hours }, factor_frequency,
# dollars_deterred: { estimate, currency:"USD", claims_counted, claims_missing_amount, methodology } }
curl -s "$API/v1/analytics/trends?bucket=week" -H "Authorization: Bearer $KEY" # counts by risk band over time
curl -s "$API/v1/analytics/factors" -H "Authorization: Bearer $KEY" # which signals fire most
In the SDK: prov.analytics.summary(), .trends({ bucket }), .factors(). Windows are capped at 366 days and default to the last 90; responses are privately cacheable for 5 minutes.
The $ deterred estimate — stated plainly
dollars_deterred.estimate is exactly the sum of claim_amount for claims a human dispositioned denied or escalated with a fraud reason code (suspected_fraud or signature_invalid). It is an estimate, not a guarantee — and it is not a black box:
claim_amountis an optional field you pass at attest (claim_amount). Claims without a recorded amount are excluded and reported asclaims_missing_amount— never imputed or guessed.- It counts only your carrier's claims (data isolation, as with fraud links), and only within the window.
- The
methodologystring travels with every response so the number is always self-describing.
Cross-claim links
Fraud rings reuse devices, locations, and cloned device keys across "unrelated" claims — the strongest signal a per-claim score can't see. On every attest, Provenance joins the new claim against your carrier's other claims (never across carriers) and records deterministic links:
shared_device(strong) — the same device backs two differently-named claims.shared_cert(strong): one device certificate reused under a differentdevice_id. A cloned key is the strongest ring signal.gps_proximity(weak) — two claims captured within a radius (default 250 m,CLAIM_LINK_RADIUS_M) and time window (CLAIM_LINK_WINDOW_HOURS).
curl -s $API/v1/claims/CLM-SANDBOX-RING-B/links -H "Authorization: Bearer $KEY"
# → { claim_id, count, data: [ { other_claim_id, link_type, strength, evidence } ] }
A link also feeds the advisory cross_claim_link risk factor on new verifications (+15 weak / +30 strong; stored scores aren't retroactively rescored — the links list is the retroactive view). In the SDK: prov.claims.links(claimId). The dashboard's claim detail shows a Linked claims panel with the shared-signal evidence and a jump to each linked claim.
Getting notified
Rather than polling for risk changes, subscribe a webhook to attestation.created, claim.provenance_updated, claim.escalated, case.status_changed, or screening.completed — Provenance pushes a signed delivery so events land in your claims system as they happen.
Claim-system connectors (Guidewire / Duck Creek)
Adjusters live in ClaimCenter and Duck Creek Claims. @provenance/connectors is a pair of stateless adapters that receive those signed webhooks and write the provenance verdict + risk score + a deep link onto the carrier claim record, and open a Provenance case when a claim is created — so authenticity signals show up where adjusters already work. The connectors verify our signatures (the SDK consumer path), are idempotent on event_id, and never block your pipeline (a platform error just triggers our webhook retry).
Availability: the connectors are fixture-verified against the platform API shapes today; sandbox certification and the marketplace listings are in progress (Enterprise feature).
Back-catalog audit — run last year's claims through us
The self-serve pilot: submit a manifest of past attestations to re-verify (and unattested media to screen) as one batch job, and get a report you can act on. Enterprise; carrier-scoped, absolute isolation.
curl -X POST $PROVENANCE_API/v1/batch/verifications \
-H "Authorization: Bearer $PRV_KEY" -H "Content-Type: application/json" \
-d '{ "items": [
{ "type": "verify", "attestation_id": "…" },
{ "type": "verify", "attestation_id": "…" },
{ "type": "screen", "ref": "media-ref" } ] }'
# → 202 { "batch_id": "…", "status": "pending" }
curl $PROVENANCE_API/v1/batch/$ID # status + progress
curl $PROVENANCE_API/v1/batch/$ID/report # on completion
The report is the artifact: per-item verdicts + risk factors, fraud-link clusters discovered across the batch (shared device / cert / GPS-proximity — the same matchers as live claims), summary stats, and an offline-verifiable signed manifest. Jobs persist, resume across restarts, and TTL-reap. Per-item failures are isolated (one bad reference never fails the job); screening honors the existing budget (batching never bypasses it); cross-carrier batches are impossible. Items meter at standard rates.