WSDOT Bid Tabulations Scraper
Pricing
from $1.44 / 1,000 item processeds
WSDOT Bid Tabulations Scraper
Export official WSDOT construction bid tabulations by letting month/year or PDF URL. Get bidder item quantities, observed unit prices, totals and PDF/page provenance.
Pricing
from $1.44 / 1,000 item processeds
Rating
0.0
(0)
Developer
Automation Lab
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Export WSDOT bid tabulations into bidder-level construction item records for Washington contractor estimating and historical bid-price benchmarking. Select a letting month/year or supply official WSDOT text-PDF URLs. Results include contract, bid-opening date, bidder identity, item description, quantity, unit, observed unit price, total amount, and PDF/page provenance.
Who is it for?
- Estimators comparing observed unit prices for similar highway work.
- Contractors reviewing bidder item prices across past lettings.
- Analysts preparing Washington transportation construction cost datasets.
These are observed bids, not recommended prices, inflation-adjusted estimates, or guarantees of future costs.
Why use it?
Bid-check reports put engineer estimates, bidder prices, totals, and percentage differences beside each other. This Actor reconstructs the text coordinates and normalizes page rotation instead of flattening all numbers into one list. It preserves bidder identity and distinguishes a unit price from a lump-sum total.
The workflow is standalone: it exports historical item-price observations, not procurement opportunity notices.
Getting started
- Open the input form.
- Enter the bid-opening year and month.
- Set the maximum bidder-item rows and maximum PDF documents.
- Run the Actor.
- Export the default dataset as JSON, CSV, Excel, or another Apify-supported format.
- Check
PARSING_REPORTbefore treating a run as complete coverage.
Example monthly input:
{"year":2025,"month":1,"maxItems":20,"maxDocuments":5}
January 2025 currently contains the XE3584 text report. The number of rows depends on bidders and bid items, not on a promised fixed dataset size.
Inputs
| Field | Meaning |
|---|---|
year | Bid-opening year, 2000–2100; required for monthly discovery |
month | Bid-opening month, 1–12; required for monthly discovery |
pdfUrls | Optional list of official WSDOT HTTPS text-PDF URLs; replaces index discovery |
maxItems | Maximum bidder-item records, default 20, maximum 100,000 |
maxDocuments | Maximum sequential PDF downloads, default 5, maximum 50 |
Supply year and month together for index discovery, or at least one PDF URL. With explicit PDFs, either optional date filter still applies to the actual bid-opening date in the document.
Explicit-PDF example:
{"pdfUrls":["https://wsdot.wa.gov/sites/default/files/2026-01/Contracts-bidtabulation-26B002.pdf"],"maxItems":20,"maxDocuments":1}
Only https://wsdot.wa.gov/sites/default/files/ PDF URLs are accepted. Other hosts, authenticated URLs, and URL query strings are rejected.
Output fields
One bidder's observation for one bid item is one dataset row. A contract with three bidders and ten items may produce thirty rows.
| Field | Description |
|---|---|
contractNumber | Contract identifier as printed, preserving leading zeroes |
jobNumber | PS&E job number, when available |
lettingDate | Bid-opening date in YYYY-MM-DD format |
bidderName | Bidder name as printed, including source truncation |
bidderId | Contractor number, when available |
itemNumber | Printed item number |
itemDescription | Printed item description |
unit | Source unit such as L.F., EACH, TON, or L.S. |
quantity | Observed estimated quantity, or null |
unitPrice | Observed bidder unit price in USD, or null |
totalAmount | Printed bidder item total in USD |
currency | USD |
parsingStatus | parsed or total_only |
sourceUrl | Official source PDF |
sourcePage | One-based physical PDF page |
Engineer estimates and percentage-difference columns are not exported as bidder prices. Contract execution and report-generation dates are not used as letting dates.
Example record
This is a representative observed item from the January 2025 XE3584 report:
{"contractNumber":"XE3584","jobNumber":"24A031","lettingDate":"2025-01-29","bidderName":"CECCANTI, INC.","bidderId":"100420","itemNumber":"3","itemDescription":"REMOVING CONC. BARRIER","unit":"L.F.","quantity":2688,"unitPrice":11,"totalAmount":29568,"currency":"USD","parsingStatus":"parsed","sourceUrl":"https://wsdot.wa.gov/sites/default/files/2025-01/Contracts-bidtabulation-24A031.pdf","sourcePage":1}
Parsing status and coverage
parsed means the parser observed both a quantity and a unit price. An observed zero unit price remains zero.
total_only means the bidder's printed item total is available but no usable unit-price observation is present, for example a lump-sum item. Quantity and unit price stay null; the Actor never divides totals to manufacture an observed price.
PARSING_REPORT records page statuses, accepted row counts, source URLs, and limit flags. Summary pages may contain no bid items. Unknown layouts and image-only pages are reported, not OCR-processed. A PDF with no supported rows fails the run rather than returning fabricated records.
A result limit may stop within a document or bidder group. A document limit may leave later contracts unvisited. Inspect the report and raise limits when necessary. Source links follow the official monthly index order; this is not a ranked or sorted search.
How much does it cost to export WSDOT bid tabulations?
Pay-per-event pricing includes a one-time run-start event and a per-row event. Each delivered bidder/item observation is one result. Diagnostics have no separate result charge.
The one-time start price is $0.025 per run. Per bidder-item row prices use Apify's qualifying monthly Store-spend tiers:
| Tier | Price per row |
|---|---|
| FREE | $0.00276 |
| BRONZE | $0.0024 |
| SILVER | $0.001872 |
| GOLD | $0.00144 |
| PLATINUM | $0.00144 |
| DIAMOND | $0.00144 |
At BRONZE, estimated Actor charges are $0.049 for 10 rows, $0.073 for 20 rows, and $0.265 for 100 rows, including the start event. A valid empty run still incurs the start event. The Store pricing panel and your account's applicable tier are authoritative. Set a run charge limit as well as input limits to control spending.
Integrations
- Export CSV to a spreadsheet; group by item description and unit before comparing prices.
- Send the dataset to a database or warehouse and deduplicate using contract, bidder identity, and item number.
- Join records to your own project characteristics; unit price alone does not control for project conditions.
- Schedule a monthly input through Apify and use a webhook to deliver the dataset. Scheduling reruns the extraction; the Actor does not maintain cross-run change detection or alerts.
API usage
Keep your Apify token in an environment variable, never in shared notebooks or committed source.
cURL
curl -X POST \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"year":2025,"month":1,"maxItems":100,"maxDocuments":5}' \'https://api.apify.com/v2/acts/automation-lab~wsdot-construction-bid-tabulations/run-sync-get-dataset-items'
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('automation-lab/wsdot-construction-bid-tabulations').call({ year: 2025, month: 1, maxItems: 100, maxDocuments: 5 });const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
Python
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ['APIFY_TOKEN'])run = client.actor('automation-lab/wsdot-construction-bid-tabulations').call(run_input={'year': 2025, 'month': 1, 'maxItems': 100, 'maxDocuments': 5})print(client.dataset(run['defaultDatasetId']).list_items().items)
MCP use
Add the Actor-specific Apify MCP endpoint to Claude Code:
claude mcp add --transport http apify \'https://mcp.apify.com?tools=automation-lab/wsdot-construction-bid-tabulations'
Claude Desktop, Cursor, and VS Code HTTP MCP configuration:
{"mcpServers": {"apify": {"url":"https://mcp.apify.com?tools=automation-lab/wsdot-construction-bid-tabulations"}}}
Example prompt: “Export January 2025 WSDOT bid-item observations, limit to 100 rows, and summarize unit prices by item and unit. Flag total-only rows instead of interpreting them as unit prices.”
Configure Apify authentication in your MCP client. AI summaries should retain source links and not imply complete coverage when a limit was reached.
Limitations
The Actor reads public WSDOT indexes and downloadable text bid-check PDFs. It does not access private accounts, solve challenges, process arbitrary uploaded PDFs, or provide scanned-PDF OCR.
Historical PDF layouts may vary. Tested examples include rotated 2023/2025 reports and unrotated 2026 reports; this is not a guarantee that every historical report is parseable. PDF upload-folder dates are not letting dates, especially for migrated archives.
Downloads are sequential with bounded transient retries and request timeouts. PDFs above 20 MB or 500 pages are rejected. Source outages, non-PDF responses, and unsupported all-empty documents fail visibly; earlier accepted rows may remain in a failed run's dataset.
Legality and responsible use
Use public records responsibly, observe applicable source terms and laws, and confirm important pricing decisions against the source document. This Actor is independent and is not endorsed by WSDOT.
Data handling and support
The runtime uses no AI or paid third-party data API. It downloads public source documents directly and holds PDF bytes transiently in memory. Only public bidder identities and bid-item records are saved; addresses are not exported. No credentials or source PDF text are logged.
Datasets, parsing reports and run logs remain in your Apify storage under your account's retention settings until you delete them. The Actor creates no external cross-run cache. Delete a run's dataset and key-value store through Apify Console or the API to remove stored results. Source documents remain published by WSDOT independently. Apify hosts execution and storage under its platform terms.
For extraction problems, open the Actor's Apify Issues tab with input, run link and the source/page affected. Avoid posting credentials or private information.
Related workflows
This Actor is intentionally standalone: a procurement-notice scraper supplies opportunities, not bidder item-price history. Combine this dataset with your own project records rather than substituting unrelated notice records for unit-price observations.
FAQ and troubleshooting
Why is a unit price null?
A lump-sum or estimated-total item may have no meaningful printed unit price. Use totalAmount and inspect parsingStatus rather than treating null as zero.
Why are there multiple rows for an item? Each bidder is a separate observation. Later PDF pages may show additional bidder columns.
Why did a month return no rows?
The selected year/month may have no linked contracts, or supplied PDFs may not match optional date filters. Check PARSING_REPORT. Failed downloads and unsupported documents are errors, not valid empty source results.
Why did only part of a contract appear?
Raise maxItems; a row limit can stop within the PDF. Raise maxDocuments to include later linked contracts.
Can I use it for every DOT? No. This Actor supports official Washington WSDOT monthly indexes and compatible WSDOT text-PDF layouts only.
Does scheduling detect changed prices? No. Store results externally and compare runs yourself. The Actor deduplicates within a run, not across runs.