# WSDOT Construction Bid Item Price Index (`kevinserver24/dot-bid-tabulation-price-index`) Actor

Awarded low-bid unit prices for every line item on Washington State DOT highway/bridge construction contracts, parsed live from WSDOT's own bid-tabulation PDFs across three incompatible report layouts. Charged only per delivered price row.

- **URL**: https://apify.com/kevinserver24/dot-bid-tabulation-price-index.md
- **Developed by:** [Kevin](https://apify.com/kevinserver24) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 price row delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## WSDOT Construction Bid Item Price Index

*An independent tool. It is not affiliated with, endorsed by, or operated
by the Washington State Department of Transportation. WSDOT publishes its
bid-tabulation records as a matter of public record under Washington's
Public Records Act, RCW 42.56 (<https://apps.leg.wa.gov/rcw/default.aspx?cite=42.56>).
This Actor reads WSDOT's own public bid-tabulation index and PDF reports
and reports what it finds, parsed and reshaped into one schema.*

**Live per run.** Every run fetches the requested month(s)' index page and
every contract PDF listed on it fresh, straight from wsdot.wa.gov.

***

### Why this exists

Washington highway/bridge construction bid prices are public, but reading
them means opening one PDF per contract by hand, from an index that lists
six years of history on twelve calendar-month pages rather than one
year-month page per month. The PDFs themselves come from at least three
different report-generator variants: which visual column is labelled "LOW
BIDDER" shifts per contract (sometimes the middle column, not the first),
and a contract with more than 3 bidders prints its entire item table again
for the next group of bidders -- a naive parser double-counts every item.
Roughly 1 in 5 real WSDOT bid-tab PDFs cannot be read as text at all (a
"Microsoft: Print To PDF" export with no recoverable font, or a font with
no usable character mapping) and this Actor detects and reports that
honestly instead of guessing or crashing. DOTestimate, the closest
self-service comparable, indexes 22 states but not Washington as of
2026-08-31 (see `DOSSIER.md`).

### What you get per row

| Field | Meaning |
|---|---|
| `state` | Always `WA` |
| `contract_number`, `ps_e_job_number`, `wsdot_project_number` | WSDOT's own contract identifiers |
| `county`, `region`, `highway` | Where the work is |
| `project_title` | The project's own description, from WSDOT's index page |
| `bids_opened_on`, `awarded_on` | Contract dates, where the source provides them |
| `item_number`, `item_category`, `item_label` | The bid item: its number, its section (e.g. "PREPARATION", "GRADING"), and WSDOT's own standard pay-item name |
| `quantity`, `unit_of_measure` | How much, in what unit |
| `engineer_estimate_unit_price`, `engineer_estimate_amount` | WSDOT's own pre-bid estimate, per unit and extended, where the item type carries a per-unit price |
| `low_bid_unit_price`, `low_bid_amount` | The AWARDED low bidder's price -- per unit and extended -- the number that determined who won |
| `low_bid_contractor` | The company awarded the contract |
| `number_of_bidders` | How many companies bid on this contract |
| `source_url` | Direct link to WSDOT's own PDF, to verify |
| `source_month` | Which requested `YYYY-MM` this row came from |
| `charged` | Whether this row cost you anything |
| `not_charged_reason` | Why it did not, in plain English |

### The billing rule, stated plainly

One event: **price row delivered**, charged once per unique
(contract\_number, item\_number) in the result. A contract that shows up
again on a later run within the same month is charged again -- the same
way `puc-rate-case-tracker` charges again for a docket that develops
further, not a duplicate of the first charge.

### Coverage -- read this before you rely on it

**v1 covers Washington State (WSDOT) only.** Oregon (ODOT) has a real,
machine-parseable bid-item archive too, but DOTestimate's own FAQ already
names Oregon as next on its roadmap -- treated as a future candidate, not
built into v1. Not every WSDOT contract PDF can be read: `SUMMARY.json` in
the run's key-value store reports exactly how many contracts were seen,
how many parsed, and how many were unparseable and why.

***

### Input

```json
{
  "months": ["2026-08"]
}
```

Leave `months` empty to check only the current calendar month -- WSDOT's
own index groups every contract by calendar month across all years on one
page, so requesting a broad range can mean a large, slow, expensive run.
Invalid entries are ignored; if nothing valid remains, the current month is
used.

### Output

```json
{
  "state": "WA",
  "contract_number": "XE3760",
  "ps_e_job_number": "26Y012",
  "wsdot_project_number": "XE3760",
  "county": "KING",
  "region": "SCR",
  "highway": "SR 090",
  "project_title": "I-90, EB 3 Miles E of North Bend - Emergency Seeding and Erosion Control Small Works Roster",
  "bids_opened_on": "AUG 12 2026",
  "awarded_on": "AUG 13 2026",
  "item_number": "2",
  "item_category": "EROSION CONTROL AND ROADSIDE PLANTING",
  "item_label": "COIR LOG",
  "quantity": 265.0,
  "unit_of_measure": "L.F.",
  "engineer_estimate_unit_price": 25.0,
  "engineer_estimate_amount": 6625.0,
  "low_bid_unit_price": 20.5,
  "low_bid_amount": 5432.5,
  "low_bid_contractor": "SCARSELLA BROS., INC.",
  "number_of_bidders": 5,
  "source_url": "https://wsdot.wa.gov/sites/default/files/2026-08/Contracts-bidtabulation-26Y012.pdf",
  "source_month": "2026-08",
  "charged": true,
  "not_charged_reason": ""
}
```

A `SUMMARY.json` lands in the key-value store with which months were
requested, how many contracts were seen/parsed/unparseable, and a
charged/free breakdown.

# Actor input Schema

## `months` (type: `array`):

Which calendar month(s) of WSDOT bid tabulations to fetch, e.g. "2026-08". Leave empty to fetch only the CURRENT month -- WSDOT's own index groups every contract by calendar month across all years back to 2000 on one page, so requesting a broad range can mean a large, slow, expensive run; the default keeps a first run small. Invalid entries are ignored.

## Actor input object example

```json
{
  "months": []
}
```

# Actor output Schema

## `results` (type: `string`):

One row per line item: contract, project, item, quantity, unit, engineer's estimate, and the awarded low bidder's price.

## `resultsCsv` (type: `string`):

The same rows as a spreadsheet.

## `summary` (type: `string`):

Which months were requested, how many contracts were seen/parsed/unparseable, and a charged/free breakdown.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("kevinserver24/dot-bid-tabulation-price-index").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("kevinserver24/dot-bid-tabulation-price-index").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call kevinserver24/dot-bid-tabulation-price-index --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kevinserver24/dot-bid-tabulation-price-index"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/amUxsY24px1Tfp47r/builds/k2zH2DMcPruorPCFo/openapi.json
