Insurance Loss Run Extractor: Loss Run PDF to Excel & CSV avatar

Insurance Loss Run Extractor: Loss Run PDF to Excel & CSV

Pricing

from $40.00 / 1,000 claim validateds

Go to Apify Store
Insurance Loss Run Extractor: Loss Run PDF to Excel & CSV

Insurance Loss Run Extractor: Loss Run PDF to Excel & CSV

Insurance loss run extractor: turn carrier loss run PDFs (Travelers, Hartford, Liberty, Zurich, CNA, Chubb, AmTrust, state funds, TPAs) into validated claims and policy-year totals in Excel, CSV and JSON: paid/reserve/incurred, frequency, severity, large losses. Rules-based, no AI.

Pricing

from $40.00 / 1,000 claim validateds

Rating

0.0

(0)

Developer

Haniel Azevedo

Haniel Azevedo

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 days ago

Last modified

Security

Limited permissions

Share

Insurance Loss Run Extractor: PDF loss runs to JSON, CSV and Excel

Send carrier or TPA loss run PDFs. You get back every claim and policy-year total as structured data: paid, reserve and incurred split into indemnity, medical and expense, plus recoveries. Each row is checked: paid + reserve = incurred, and the result must agree with the totals printed in the report. You only pay for rows that pass those checks.

  • Carriers with layout rules: Travelers, The Hartford, Liberty Mutual, Zurich, CNA, Chubb, AmTrust, Employers, State Compensation Insurance Fund and other state funds, Sedgwick, Gallagher Bassett, AIG, TASB-style pools, Sompo. A generic table reader handles everything else.
  • Lines of business: Workers' Comp (WC), General Liability (GL), Auto and Property. One PDF can bundle several carriers, lines and policy years.
  • Outputs: a JSON dataset, plus CLAIMS.csv, POLICY_YEARS.csv, CARRIER_SUMMARIES.csv and LOSS_RUNS.xlsx in the run's key-value store.
  • Analytics: totals per policy year, frequency, severity, large-loss flags and your largest claims.
  • Pricing: $0.04 per validated claim and $0.02 per validated policy-year row. Rows that do not reconcile, failed files and the demo file are free.
  • Rules only. No AI or LLM, and no third-party API. Text PDFs are read directly. Scanned pages can optionally go through Tesseract OCR.

Re-keying a 5-year loss run into a spreadsheet takes an account manager or underwriter about 20–40 minutes. This Actor does it in seconds and tells you which numbers it could prove.

Who it is for

You areUse it to
Agent / brokerBuild marketing submissions and stewardship reports, and compare years across carriers in one sheet.
UnderwriterGet frequency, severity, large losses and open reserves per policy year without re-keying.
Insurtech developerCall one API that takes a PDF and returns validated JSON, with stable field names and error codes.
AI agentUse deterministic numbers with a validated flag, instead of asking a model to read a table.

Example input

The Console form is prefilled with a real public loss run: a 2-page Travelers property loss run published by New Braunfels ISD (Texas). Click Start without changing anything:

{
"pdfUrls": [
"https://nbisd.org/wp-content/uploads/2024/06/NBIndependentSchoolLossRunsTravelers.pdf"
]
}

Example output

That run takes about 13 seconds and returns 2 claims, both validated, and all 3 printed report totals reconciled. The demo file is free. One claim row (run of 1 Nov 2026, trimmed):

{
"type": "claim",
"carrier": "Travelers",
"lineOfBusiness": "PROPERTY",
"lineOfBusinessRaw": "P - PROPERTY",
"policyYear": 2020,
"valuationDate": "2024-01-02",
"claimNumber": "FHZ8795",
"lossDate": "2021-02-17",
"reportDate": "2021-03-10",
"closeDate": "2021-04-25",
"status": "closed",
"description": "ACCORDING TO THE INSURED, TWO BOILERS SUFFERED DAMAGE IN NATURE.",
"paid": {
"indemnity": 0,
"medical": 0,
"expense": 600,
"total": 600
},
"reserve": {
"indemnity": 0,
"medical": 0,
"expense": 0,
"total": 0
},
"incurredTotal": 600,
"largeLoss": false,
"validation": {
"validated": true,
"arithmetic": "valid",
"formula": "paid+reserve",
"reportTotals": "reconciled",
"flags": []
},
"source": {
"fileName": "NBIndependentSchoolLossRunsTravelers.pdf",
"page": 1
},
"charged": false,
"demo": true
}

The OUTPUT record adds overview.byLineOfBusiness (here PROPERTY: 2 claims, $600 incurred, severity $300) and links to CLAIMS.csv, POLICY_YEARS.csv, CARRIER_SUMMARIES.csv and LOSS_RUNS.xlsx.

Quick start (API first)

curl -X POST "https://api.apify.com/v2/acts/hanielazevedo~insurance-loss-run-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"pdfUrls": ["https://nbisd.org/wp-content/uploads/2024/06/NBIndependentSchoolLossRunsTravelers.pdf"], "redactClaimants": "initials"}'

Python:

from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("hanielazevedo/insurance-loss-run-extractor").call(run_input={
"pdfUrls": ["https://nbisd.org/wp-content/uploads/2024/06/NBIndependentSchoolLossRunsTravelers.pdf"],
# "pages": "94-104", # optional: loss runs inside a bigger RFP / submission
"largeLossThreshold": 100000,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["type"] == "claim":
print(item["claimNumber"], item["incurredTotal"], item["validation"]["validated"])
summary = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("OUTPUT")["value"]
print(summary["overview"]["byLineOfBusiness"])
  1. In Apify Console, open Storage → Key-value stores and create a store, for example loss-runs.
  2. Upload your PDFs as records, for example acme-wc-2025.pdf.
  3. In the Actor input, pick that store in PDFs from your key-value store. Optionally list the record keys. If you leave the keys empty, every *.pdf record is read.

The Actor runs with limited permissions. Picking the store gives that one run read-only access to that one store, and nothing else.

Step by step (Console)

  1. Paste one or more PDF links into Loss run PDF URLs, or pick a key-value store.
  2. Optional settings:
    • Pages (for example 3-8,12) when the loss run sits inside a larger document.
    • Claimant privacy: initials, hash or remove.
    • Large-loss threshold.
    • Rounding tolerance.
  3. Click Start.
  4. Open the Output tab to see the claims table, or download LOSS_RUNS.xlsx from the run's key-value store. The link is also in OUTPUT.downloads.

Input

FieldTypeDefaultWhat it does
pdfUrlsarray of URLs–Public http(s) links to loss run PDFs.
pdfStoreKV store (read-only picker)–A store holding your PDFs.
pdfKeysarrayall *.pdfRecord keys in that store.
pagesstringall1-based pages, for example 94-104 or 3-8,12. Applies to every file.
redactClaimantsnone / initials / hash / removenonePrivacy for claimant and injured-worker names. hash gives a stable token within a run.
largeLossThresholdinteger100000Incurred at or above this amount sets largeLoss: true.
toleranceAbsnumber1Allowed rounding per term (plus 0.05% relative).
includeCarrierSummariesbooleantrueAlso return the carrier's own policy-year summary lines.
enableOcrbooleantrueOCR scanned pages with Tesseract.
maxPages / ocrMaxPagesinteger60 / 15Page limits per file.
maxFileSizeMbinteger25 (max 50)Download size limit.

Output

The dataset has one item per row. The type field is one of:

  • claim: one row per claim;
  • policyYearSummary: a carrier-printed summary line;
  • error: a file that failed.

Claim item

{
"type": "claim",
"carrier": "Chubb",
"writingCompany": "FEDERAL INSURANCE COMPANY",
"insured": "WESLACO I.S.D",
"policyNumber": "79580818",
"lineOfBusiness": "PROPERTY",
"lineOfBusinessRaw": "INLAND MAR",
"policyEffective": "2012-09-01",
"policyExpiration": "2013-09-01",
"policyYear": 2012,
"valuationDate": "2018-06-19",
"claimNumber": "040512146091",
"claimant": "Weslaco I.s.d.",
"lossDate": "2012-12-25",
"reportDate": "2012-12-26",
"closeDate": "2013-02-21",
"status": "closed",
"description": "A backhoe was stolen.",
"location": "Unknown, TX",
"paid": {"indemnity": 35146.0, "expense": 0.0, "total": 35146.0},
"reserve": {"indemnity": 0.0, "expense": 0.0, "total": 0.0},
"incurred": {"total": 35146.0},
"recoveries": 0.0,
"deductible": 0.0,
"incurredTotal": 35146.0,
"largeLoss": false,
"validation": {"validated": true, "arithmetic": "valid", "formula": "paid+reserve", "reportTotals": "not-printed", "flags": []},
"source": {"fileName": "Chubb_Federal.pdf", "pdfUrl": "https://…/Chubb_Federal.pdf", "page": 2},
"charged": true
}

What the claim fields mean:

  • lineOfBusiness: WC, GL, AUTO, PROPERTY, another normalised value, or null. lineOfBusinessRaw keeps the carrier's own wording.
  • status: open, closed or reopened.
  • Amounts: a component that a carrier does not print is simply absent, never invented.
  • validation.formula shows which identity held: paid+reserve, paid+reserve+deductible (pools that print incurred including the member deductible), paid+reserve-recovery, or similar.
  • validation.reportTotals:
    • reconciled: the row's section adds up to a printed subtotal;
    • not-printed: there is no total to check against;
    • mismatch: there is a printed total and it disagrees, so the row is not validated.

Policy-year table (POLICY_YEARS.csv, Excel sheet "Policy years", OUTPUT.policyYears)

There is one row per carrier, line and policy year, with these columns:

  • claimCount, openCount;
  • paid, reserve, incurred and recoveries;
  • severity (incurred per claim), largeLossCount and largeLossIncurred;
  • basis: claimDetail (computed from claim rows), carrierSummary (the carrier's printed summary), or noLossesReported.

OUTPUT record

The OUTPUT record holds:

  • the run counts and chargedEvents;
  • downloads: links to the CSV and XLSX files;
  • overview.byLineOfBusiness: years, claim count, incurred, frequency per year, severity and large losses;
  • largestClaims;
  • per-document validation stats;
  • errors.

Validation and billing (pay per event)

EventPriceWhen it is charged
claim-validated$0.04A claim row where paid + reserve = incurred within tolerance (allowing for a printed deductible or recovery convention), that does not contradict any printed report total, and that has a claim number or loss date.
policy-year-validated$0.02A carrier policy-year summary row that passes the same checks.

Example: a 5-year loss run with 40 validated claims and 5 validated policy-year rows costs $1.70. An Actor start fee of $0.00005 per GB of memory applies once per run.

These are always free:

  • rows that do not reconcile (validated: false, charged: false);
  • failed files and error items;
  • scans that cannot be read;
  • non-loss-run PDFs;
  • the demo prefill file.

If the next file's validated rows do not fit in your maximum total charge, the Actor skips that file whole and returns a BUDGET_EXCEEDED error item. You never get a half-billed file.

Error codes

CodeMeaning
INVALID_INPUT / INVALID_URL / BLOCKED_URLBad input or URL. Private and internal addresses are refused.
DNS_ERROR / DOWNLOAD_FAILED / DOWNLOAD_TIMEOUT / HTTP_ERROR / NOT_FOUNDThe download failed (retryable tells you whether retrying may help).
ACCESS_DENIEDThe run cannot read the chosen store.
FILE_TOO_LARGE / EMPTY_FILE / UNSUPPORTED_FORMAT / CORRUPT_FILE / ENCRYPTED_PDFThere is a problem with the file.
SCANNED_NOT_SUPPORTED / OCR_FAILEDThe file is image-only and OCR is off or failed.
NOT_LOSS_RUNNo claim or policy-period table with money columns was found.
NO_DATA_FOUNDIt looks like a loss run, but no row could be read (unsupported layout).
PARSE_FAILEDUnexpected parser error.
BUDGET_EXCEEDEDYour max total charge was reached.

Accuracy

Accuracy is measured with pytest (tests/test_accuracy.py) and is field-level: claim number, dates, status, claimant, description, policy number and period, line, and every printed amount.

SetClaimsField accuracyCharged rows with broken arithmetic
Synthetic dev (12 carrier styles × 3)599100%0
Synthetic held-out (frozen, unseen seeds and print variants)385100%0
Hand-checked public loss runs (Travelers, Chubb, TASB)9100%0

Real public loss runs, validated rows out of rows read:

Loss runClaims validatedSummaries validated
Chubb2/25/5
Travelers2/2–
Liberty Mutual summaries–80/80
AIG bundle12/121/7
TASB (two RFPs)37/37–
Zurich (truncated print)0/6–

The synthetic sets test the layout rules and the charging guard. They are cleaner than real-world prints, so expect lower numbers on unusual layouts. In those cases the rows come back flagged and free, not wrong and billed.

FAQ

Does it use AI? No. It reads the PDF's text geometry: header bands, column stacks, row records and totals blocks. Then it checks the arithmetic. The same file always gives the same result.

What if my carrier is not in the list? The generic table reader handles most "one row per claim" layouts with labelled money columns. Rows it can read but cannot prove come back flagged and free.

Scanned loss runs? OCR is available. However, OCR'd amounts rarely reconcile exactly, so those rows usually come back unvalidated and free. Ask the carrier for the native PDF when you can.

Where is my Excel file? It is the LOSS_RUNS.xlsx record in the run's default key-value store. The link is in OUTPUT.downloads.

Is claimant data stored? Only in your own run's dataset and store. Use redactClaimants to mask names before they are written.

Why is a total row "count-only"? Some prints show only claim counts in their totals. A count alone never validates a row's money.

Limitations

  • Text PDFs work best. Amounts read by OCR are unreliable and almost never billed.
  • If the carrier's own print is truncated or misaligned, its rows come back unvalidated (as with the Zurich sample).
  • Some layouts print no claim number. Those rows use the loss date as the key.
  • Pool or TPA prints without a carrier name return carrier: null.
  • The carrier name is detected from page text. The writing company is reported separately.
  • Hartford summary pages sometimes print adjacent rows glued together, and those rows stay flagged.
  • Per file: at most 60 pages, of which at most 15 OCR'd, and at most 50 MB.

Other Actors by Haniel