Price List to CSV – Clean Price Sheet Parser avatar

Price List to CSV – Clean Price Sheet Parser

Pricing

from $140.00 / 1,000 price list processeds

Go to Apify Store
Price List to CSV – Clean Price Sheet Parser

Price List to CSV – Clean Price Sheet Parser

Turn a messy price list, rate card, supplier sheet or service menu into a clean CSV, Excel or JSON table in 3 steps: paste it or link it, choose CSV, click Start. One row per item with price, unit and currency. Rule-based, so it never invents a price. $0.20 per list.

Pricing

from $140.00 / 1,000 price list processeds

Rating

0.0

(0)

Developer

Joao Cassimiro

Joao Cassimiro

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Security

Limited permissions

Share

Turn a messy price list, rate card or service menu into a clean CSV or JSON table. One row per item with the price, unit, currency and a flag on anything worth a second look. Paste the text, or give a public web page, .txt, .csv or text PDF link, then press Start.

▶️ Watch the 39-second demo: messy price sheet to clean CSV in 3 steps.

How it works (3 steps)

  1. Paste your price list into Price list (paste it here). Or put a link to a public web page, .txt, .csv or text PDF file in Price list URL.
  2. Choose CSV under Output format (or keep JSON).
  3. Click Start. When the run finishes, the Output tab shows one clean row per item. Download it as CSV, Excel or JSON.

Works for a supplier price list, rate card, service menu, restaurant or catering menu, contractor price sheet or price book. Have a PDF price list? Put its link in Price list URL (see the FAQ).

By default it works by rules, not AI, so it never invents a price: every number comes from your sheet, next to the original text (price_text, raw) and line number (source_line).

You give itYou get backYou pay
Pasted text, or a public web page, .txt, .csv or text PDF URLOne row per item: description, price or low–high range, unit, currency, flags, source line. Plus a SUMMARY of every line that wasn't read as an item$0.20 per price list (up to 1,000 rows). $0 if no prices are found

What does this price list parser do?

It reads each line of a price sheet and decides what the item is, what it costs and in what unit. You get:

  • description: the item or service, with the price cut out. Minimums, add-ons and notes stay in the text.
  • price: a single number. Or price_low / price_high for a range such as $40-$60/hr. Or non_price with the sheet's own words, such as "call for pricing", "TBD", "price on request" or "negotiable".
  • unit, normalized: each, hour, day, week, month, year, visit, flat, sq ft, 1k sq ft, lin ft, cu yd, cu ft, yard, load, trip, mile, gal, lb, ton, pane, vent, site, item, application.
  • multi-buy deals: for "2 for $5", price is the per-unit price (2.50, unit each). deal_quantity (2) and deal_total (5) keep the deal as written.
  • currency: USD, EUR, GBP or JPY from the symbol ($ is read as USD), or a written code (USD, EUR, GBP, CAD, AUD, NZD, JPY). Left empty when the sheet doesn't say.
  • quantity: counts such as "3 units" or "2 @ $5", kept separate so a count is never taken as a price.
  • tiers: when a row has more than one per-unit price (side-by-side columns such as residential and commercial, or a discounted price), all of them are kept under the keys primary, alt_1, alt_2, and deal_N_for_M for multi-buy totals.
  • flags on uncertain rows, plus source_line and raw on every row.

It reads dot leaders (Mow ....... $45), dashes, tabs, bullets and real CSV files. For a CSV with headers, it finds the item, price, unit and quantity columns by their names. For a web page, it pulls out HTML table rows and the remaining text.

Why use it instead of asking a chatbot? It never invents a price

A chatbot that can't read a line still returns a plausible number, so a minimum charge or trip fee becomes the price and nobody notices. This Actor works the other way round:

  • No made-up prices. If a line has no price, you get the sheet's own words ("call for pricing"), never a number.
  • The right number on the line. Minimums, add-ons and surcharges aren't taken as the price. "Hedge trimming - $3.50 per linear ft, minimum $85" comes out as 3.50 per lin ft, with "minimum $85" kept in the description.
  • Every row can be traced. price_text is the price exactly as written, raw is the whole original line, and source_line is its line number, so you can check any row against the sheet in seconds.
  • Nothing disappears. Headings and terms lines (like "All prices subject to a 3% fuel surcharge") are listed in the SUMMARY record with a reason, not dropped silently or turned into fake items. A line with a number but no readable price stays as a row flagged no_price_found.
  • Same input, same output, which matters in pipelines and agent workflows.

These promises apply to the default rule-based mode. Optional AI mode (below) uses a language model and is less predictable.

What do the flags mean?

There are 14 flag types. A row can have several.

FlagWhat it means
duplicate_conflicting_priceThe same item appears more than once with different prices
price_outlier_highAt least 20× the median price for its unit (checked when 5+ rows share that unit); often a total row or typo
price_outlier_low1% of the median for its unit or less (same 5-row rule)
contradictory_pricingA price and a phrase like "call for quote" on the same line
approximate_price"from" or "starting at"
ambiguous_number_format1.250 could be 1.25 or 1,250 (read as 1.25)
multiple_price_candidatesSeveral figures on one line; the likeliest is the price, all are in tiers
range_presentA range sits beside a single price ("Crew of 2-3 movers $120/hr")
range_reversedA range written high to low; stored low to high
multibuy_dealA deal like "2 for $5"; per-unit price worked out, deal kept
unit_price_roundedThe per-unit split isn't exact ("3 for $10" = 3.33)
ambiguous_at_price"2 @ $5" read as 2 at $5 each
promotion"2 for 1", BOGO or similar
no_price_foundLooks like an item, but no price could be read (not billed)

Who uses a price list to CSV converter?

  • Contractors and home-service businesses: landscaping, cleaning, HVAC or plumbing price sheets going into Jobber, Housecall Pro, QuickBooks or a spreadsheet.
  • Restaurants and caterers: menus and catering price lists going into a POS or website.
  • Procurement and ops teams: supplier and vendor rate cards put into one format so they can be compared.
  • Agencies: price pages and quote calculators for local clients.
  • Developers and AI-agent builders: a price-list parser that gives the same answer every time.

How do I convert a price list to CSV or Excel?

  1. Click Try for free.
  2. Paste your price list into Price list (paste it here), or enter a Price list URL.
  3. Under Output format, choose JSON or CSV, then click Start.
  4. Open the Output tab to see the table, and export it as CSV, JSON or Excel. If you chose CSV, a ready-to-open OUTPUT.csv is also saved in the run's key-value store.

Options:

  • Reject rows with no price (off by default) keeps only rows with a number. "Call for pricing" rows are dropped.
  • Use an LLM for genuinely unstructured input (off by default) is described under AI mode below.

Input example

This is the example pre-filled in the input form:

{
"priceListText": "Lawn & Landscape\nMow, edge, blow .......... $45 / visit\nMowing larger lot (1/2 acre) ..... $65\nHedge trimming - $3.50 per linear ft, minimum $85\nLeaf cleanup - $150 flat, add $50 if more than 6 bags\nMulch install - $85 per cubic yard, includes labor\nWeed/feed 7-step program: call for pricing\nSpring cleanup bundle (leaf + bed + first mow) $299\nFertilizer application $60/1k sq ft\nAeration & overseed $0.18 per sq ft\nSnow removal (seasonal contract) - quoted per site, typical $95/visit\n\nALL PRICES SUBJECT TO A 3% FUEL SURCHARGE. Free estimates. Call Mike at (515) 555-0142.",
"outputFormat": "csv"
}

Output example

This is real output from the input above (first rows shown; one dataset item per row; fields that don't apply are left out):

[
{"description": "Mow, edge, blow", "price": 45.0, "price_text": "$45 / visit", "currency": "USD", "unit": "visit", "source_line": 2, "raw": "Mow, edge, blow .......... $45 / visit"},
{"description": "Hedge trimming - minimum $85", "price": 3.5, "price_text": "$3.50 per linear ft", "currency": "USD", "unit": "lin ft", "source_line": 4, "raw": "Hedge trimming - $3.50 per linear ft, minimum $85"},
{"description": "Leaf cleanup - flat, add $50 if more than 6 bags", "price": 150.0, "price_text": "$150", "currency": "USD", "unit": "flat", "source_line": 5, "raw": "Leaf cleanup - $150 flat, add $50 if more than 6 bags"},
{"description": "Weed/feed 7-step program", "non_price": "call for pricing", "source_line": 7, "raw": "Weed/feed 7-step program: call for pricing"},
{"description": "Aeration & overseed", "price": 0.18, "price_text": "$0.18 per sq ft", "currency": "USD", "unit": "sq ft", "source_line": 10, "raw": "Aeration & overseed $0.18 per sq ft"}
]

The same rows in OUTPUT.csv use these columns:

description,price,price_low,price_high,unit,currency,quantity,deal_quantity,deal_total,non_price,tiers,flags,source_line
"Mow, edge, blow",45.0,,,visit,USD,,,,,,,2
Hedge trimming - minimum $85,3.5,,,lin ft,USD,,,,,,,4
Weed/feed 7-step program,,,,,,,,,call for pricing,,,7

In the CSV, flags are joined with ; and tiers is JSON.

The SUMMARY record (key-value store) gives counts, warnings and every line that wasn't read as an item:

{
"items": 10, "priced": 9, "unpriced": 1, "unparsedLines": 2, "flagged": 0,
"flagCounts": {}, "warnings": [],
"unparsed": [
{"line": 1, "text": "Lawn & Landscape", "reason": "no price and no number - probably a heading"},
{"line": 13, "text": "ALL PRICES SUBJECT TO A 3% FUEL SURCHARGE. Free estimates. Call Mike at (515) 555-0142.", "reason": "terms or note, not a line item"}
]
}

warnings also catches problems across rows: duplicates with different prices, outliers, and sheets where most priced rows have no unit.

Try it with one of these

Four ready-made inputs. Paste any of the first three into the text box, or point the URL field at the fourth, and you get the table shown without formatting anything yourself.

1. A supplier price list — the case that breaks naive parsers

Every size on this sheet is a number, and a parser that takes "the number on the line" prices the water bottle at 750.

Valley Wholesale - Beverage Price List (effective 1 Nov 2026)
Still water, 750ml bottle VW-750 $2.40 ea
Sparkling water, 1L bottle SP-1L $3.15
Orange juice, 2L OJ-2L $4.50 / bottle
Cold brew concentrate, 1L CB-1L $12.00
Mineral water, 12 x 500ml case MW-12 $18.00 / case
Tonic syrup, 500ml TS-500 call for pricing

6 rows, five with prices — 2.40, 3.15, 4.50, 12.00, 18.00 — and units of each, bottle and case. Tonic syrup returns with call for pricing and no price. The dated title produces no row at all.

2. A service rate card — per-unit rates and stated minimums

GreenLeaf Lawn & Landscape - Rate Card
Mow, edge, blow $45 / visit
Hedge trimming $3.50 per linear ft, minimum $85
Weed / feed 7-step program call for pricing
Aeration & overseed $0.18 per sq ft
Mulch install $85 per cubic yard, includes labour
Snow removal (seasonal) quoted per site, typical $95 / visit
Spring cleanup bundle $299

7 rows, with units of visit, lin ft, sq ft and cu yd. Hedge trimming keeps minimum $85 attached to the 3.50 rate it constrains, rather than reporting 85 as the price or discarding the conditional.

3. A messy quote sheet — ranges, TBD and "from"

Tree Services Quote Sheet - updated 27 Oct
Tree removal (up to 24 in dia.) $450 - $1,200
Stump grinding $8 per inch
Emergency call-out (after hours) $250
Crown thinning (over 60 ft) TBD
Lot clearing from $1,800
(All prices subject to a 3% fuel surcharge. Free estimates.)

5 rows. The range stays a range (450–1200) instead of becoming two guesses; TBD survives as a row because its absence is information; from $1,800 is reported and flagged approximate_price rather than presented as a firm price; and the surcharge footnote is not a row.

4. A PDF — the same job from a document

Sample PDFhttps://jmgc-drop.pages.dev/actor-samples/sample-price-list.pdf
Sizeone page, 2.5 KB, text-based (no OCR needed)
Contentsfictional business data — "Meadowbrook Supply Co"

Put that URL in the Price list URL field. 14 rows come back, covering a dated title, a percentage footnote, size numbers inside descriptions, a call for pricing row, a range, a from price and a TBD — and the units each, bottle, case, visit, lin ft, sq ft, cu yd and in.

How much does it cost to convert a price list?

You pay per event. There's no subscription and no monthly fee for this Actor.

What you runCost
1 price list (up to 1,000 rows)$0.20 (less on paid Apify plans, see below)
10 price lists$2.00
A 1,500-row catalog$0.40 (one charge per started 1,000 rows)
Optional AI mode, only when it's on and returns rows+$0.03 per list (paid Apify plans only; free-plan users aren't charged it)
Empty input, or nothing with a price, range or "call for pricing" found$0
  • Only rows with a price, range or "call for pricing"-type phrase count toward the 1,000. Text with bare numbers can still be read as a price list and charged, so check the input first.
  • Paid Apify plans pay less per list: $0.18 on Bronze (Starter), $0.16 on Silver (Scale), $0.14 on Gold (Business).
  • Free plan: Apify's free plan includes $5 of usage each month, enough for up to 25 price lists at $0.20, so you can test the Actor on your own sheets first.

How do I import a price list into Jobber or Housecall Pro?

Run with CSV output, open OUTPUT.csv in a spreadsheet, check flagged rows, then rename and add columns as below. Column names are from each tool's official help page (checked Oct 28, 2026). The Actor doesn't write these files directly, and import formats can change, so check the current instructions first.

Jobber (Settings → Products & Services → Import Products & Services)

Jobber's help page says to download its sample CSV and keep these headings: Name, Description, Unit Price, Taxable, Category, Active.

Jobber headingTake it fromNotes
Namedescription
Descriptionunit (e.g. "per visit"), plus any notesJobber's sample file has no unit column
Unit PricepriceNumbers only; Jobber says a $ breaks the import. The Actor's price column is already a plain number
TaxableYou fill in TRUE or FALSEBlank means taxable
CategoryYou fill in Service or ProductCase-sensitive; blank means Product
ActiveYou fill in TRUE or FALSE
  • Rows without a single price: ranges (price_low / price_high) and non_price rows have no single number, so decide those by hand.
  • Large lists: Jobber limits one import to 2.5 MB.
  • QuickBooks sync: if you sync with QuickBooks US, Jobber says to update items in QuickBooks instead.

Housecall Pro (Settings → Price Book → Services or Materials → Upload file)

Housecall Pro's help page lists these service fields: Industry (required, must match an industry in your account), Category (required), Subcategory_1, Name (required), Description, Price, Cost, Taxable and unit_of_measure (defaults to "each").

Housecall Pro fieldTake it from
Namedescription
Priceprice
unit_of_measureunit
Industry, CategoryYou fill these in

Its import wizard matches your columns to its fields and highlights errors before upload. Its examples spell units out ("square foot", "gallon"), while the Actor writes "sq ft" and "gal", so check how the wizard accepts them.

Can I use it with Zapier, Make, n8n or the API?

Yes. Apify's official integrations can start any Store Actor:

  • Zapier: the Apify app's Run Actor action. Synchronous runs must finish within 30 seconds; a typical price list finishes well inside that.
  • Make: Run an Actor, with Watch Actor Runs to pick up results.
  • n8n: the Apify node's Run an Actor and get dataset.
  • Sheets, Airtable or a CRM: send the dataset on from any of these, use a webhook when the run finishes, or schedule runs against a public price page.

From code, call jc-datatools/price-list-normalizer. The API tab has ready-made JavaScript, Python, CLI and HTTP examples. One HTTP call that runs it and returns the rows:

curl -X POST "https://api.apify.com/v2/acts/jc-datatools~price-list-normalizer/run-sync-get-dataset-items" \
-H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"priceListText": "Mow, edge, blow .... $45 / visit\nHedge trimming - $3.50 per linear ft, minimum $85"}'

Can AI agents use it? (MCP)

Yes. AI assistants and agents that support the Model Context Protocol, such as Claude, Cursor or VS Code, can call this Actor as a tool through Apify's hosted MCP server. Add this to your MCP client's configuration:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=jc-datatools/price-list-normalizer"
}
}
}

On first connection your browser opens so you can sign in to Apify. You can use an Authorization: Bearer <YOUR_APIFY_TOKEN> header instead. Loading the Actor as a tool also gives the agent the tools it needs to read the run's results (dataset items and the SUMMARY record). Agents connected to Apify's default MCP setup can also find it with search-actors.

Why it suits agents:

  • Same answer for the same sheet, in the default mode.
  • Every price traces to source_line, and unpriced items come back as words, not guesses.
  • Agents can cap spending per run with maxTotalChargeUsd in the run options.

What won't it do? (limits)

  • PDFs: text-based only, by link. A PDF link in Price list URL is read when the PDF contains real, selectable text. A PDF can't go in the paste box; use its link. PDF links served as application/octet-stream work too.
  • PDFs that are refused, with a message saying why: scanned or image-only PDFs (there's no OCR, so paste the text instead), password-protected PDFs (ones that open without a password and only block printing or copying are fine), PDFs over 40 pages, PDFs that take more than 15 seconds to read or hold far more content than a price list, and corrupt or truncated files. A refused PDF is not charged.
  • No images, Word or Excel files. Paste the text, or link a web page, .txt, .csv or text PDF file. Links to image, ZIP, Word or Excel files are refused with a message.
  • Not handled yet:
    • Prices written in words ("forty dollars").
    • "FREE": the line is listed as unparsed.
    • Bundles with a unit word in between: "2 cords for $500" is read as $500 with a multiple_price_candidates flag, not $250 per cord.
    • Units outside the list above (for example "acre") are left empty.
  • No price conversion. It doesn't convert currencies or add taxes or surcharges. It reports what the sheet says.
  • URLs: public http/https only (ports 80 and 443), at most 3 redirects, 5 MB maximum. Private and internal addresses are refused.
  • Review flagged rows before you import. On a line with several figures, the most likely one is chosen and flagged.

Optional AI mode

For price lists written as sentences instead of a list, turn on Use an LLM for genuinely unstructured input.

  • Off by default. Choose Gemini 2.5 Flash (recommended) or Gemini 2.0 Flash. The first 20,000 characters are sent through Apify's OpenRouter integration.
  • If the model fails or returns nothing usable, the parser output is kept and no AI fee is charged.
  • AI-mode rows don't carry source_line, raw or price_text, and results can vary between runs. The cross-row checks still run. Use the default mode whenever your sheet is a list.

FAQ

Can I convert a PDF price list to a spreadsheet (CSV or Excel)?

Yes, if the PDF has real text in it. Put the PDF's public link in Price list URL, choose CSV, and press Start. From the Output tab you can download CSV or Excel. A PDF on your computer needs a public link first, or copy its text out and paste it in. Scanned PDFs have no text to read, so they need OCR first.

Can I parse a supplier price list, rate card or menu?

Yes. Any list where each line has an item and a price works: supplier and vendor price lists, rate cards, service menus, restaurant and catering menus, and contractor price sheets.

Does it use AI or make up prices?

Not by default. The default mode is rule-based and never invents a price; a line with no price comes back with the sheet's own words. AI mode is off unless you turn it on.

What happens to minimum charges, trip fees and add-ons?

They're never taken as the price. They stay in the description ("Hedge trimming - minimum $85", price 3.50 per lin ft).

Can I import the result into Jobber, Housecall Pro or QuickBooks?

Yes, after renaming a few columns (see the Jobber and Housecall Pro section). For QuickBooks, use its own import template and map the same columns.

Which currencies and units does it understand?

$, €, £ and ¥ symbols, and the written codes USD, EUR, GBP, CAD, AUD, NZD and JPY. It reads 25 units, from each, hour and visit to sq ft, lin ft, cu yd, gal and ton. It reads European decimal commas ("25,50 €" = 25.50) and flags numbers that could go either way.

What if my price list has thousands of items?

It works. You pay $0.20 per started 1,000 priced rows, so 1,500 rows cost $0.40.

Am I charged if nothing is found?

No. If no price, range or "call for pricing" item is found, or the input is empty, nothing is charged.

Is my price list stored or shared?

Input and results stay in your own Apify storage. Only with AI mode on is the text sent to the selected model (through Apify's OpenRouter integration).

A line came out wrong. What should I do?

Open an issue on the Issues tab and paste the line that came out wrong and what you expected. Fixes for real-world lines are the priority.

Feedback: tell me what you like, what you don't, and what broke

This Actor is maintained by Joao Cassimiro (jc-datatools). Your feedback decides what gets fixed and built next.

  • 🐞 Something came out wrong? Open the Issues tab on this page. Paste the line, what you got and what you expected. Please remove anything private first.
  • 💡 Want a new format, unit or industry? Open an issue that starts with "Request:".
  • 👍 Did it save you time? A rating or a short review on this page helps other people find it.
  • 👎 Didn't fit your job? Open an issue and tell me why, even in one line.

Every issue is read.

Changelog

  • 2026-10-29 (0.2.4): Coupon and discount wording (for example "Get $15 off hand wash" or "Coupon: $20 off season passes") no longer reports the discount as the item price, and lines with both a saving and a real price now use the real price. No output format changes.
  • 2026-10-29 (0.2.3): Better handling of "off" wording (for example "off road tires" and "$X off
  • 2026-10-29 (0.2): More accurate prices. A SKU or part code (A-100) is no longer read as the price; "save $20", discounts, deposits and refundable amounts are no longer taken as the price; "$99 / 2 hrs" keeps its price; tiers hold per-unit prices only. Breaking: tiers keys changed from column_2, column_3, ... to primary, alt_1, alt_2, ... and deal_N_for_M.
  • 2026-10-28: First public release.