@pipeworx/gao-protests
Connect: https://gateway.pipeworx.io/gao-protests/mcp · Install: one-click buttons
Tools: 4
GAO bid-protest decisions — government-contract award disputes ruled on by the
US Government Accountability Office (“did anyone protest this award”, “how
often does this agency lose protests”). Pairs with usaspending and
dod-contract-announcements, which cover the award side but not disputes.
Tools
gao_protests_recent(days?, outcome?, limit?)— recently decided protests, optionally filtered by outcome (denied|dismissed|sustained).gao_protest(b_number)— a single decision by B-number, with the decision PDF link.gao_protests_search(protester, limit?)— search accumulated decisions by protester/company name.gao_protests_coverage()— total decisions held, earliest/most-recent decided_on, last crawl time.
Auth
Keyless. Reads a Pipeworx-hosted table (gao_bid_protests), ingested daily
by workers/data-pipeline (dataset gao-protests) — no operator key needed.
Data sources
- https://www.gao.gov/legal/bid-protests/search — the bid-protests landing page. It embeds a Drupal Views “recent decisions” block directly in the page HTML (no JS challenge), showing the ~5 most-recent decisions.
- https://www.gao.gov/products/B-XXXXXX — each decision’s own page, fetched once per new B-number for its exact decided date and PDF link.
Coverage is a rolling accumulation, not GAO’s full protest archive. The
filtered search endpoint (gao.gov/search?f[0]=ctype_search:Bid Protest Decision) sits behind an Akamai Bot Manager proof-of-work JS interstitial
that cannot be solved from a server — full Chrome headers over HTTP/2 don’t
help, this is a JS-execution challenge, not a header fingerprint. The landing
page’s own “More” link points at that same blocked endpoint, and there is no
other reachable pagination. So this pack can only ever see the decisions GAO
has published since it started crawling — gao_protests_recent/_search
answer against what has accumulated in gao_bid_protests so far, not against
GAO’s complete decision history. gao_protests_coverage reports exactly how
far back that goes.
Both hosts need the full Chrome header set (User-Agent, Accept,
Accept-Language, sec-ch-ua*, Sec-Fetch-*, Upgrade-Insecure-Requests
over HTTP/2) — a plain User-Agent alone gets a 403. This is a
header-fingerprint block, not a Cloudflare-egress block; no proxy is needed.
The protested AGENCY is not available. It appears only inside each decision’s PDF text, never in the page HTML, and this pipeline has no PDF text extraction. Rather than guess an agency from context (or from the protester’s history), the field is omitted entirely — presenting a guessed agency as fact would be worse than not having it.
A decision can carry several B-numbers (e.g. B-424484,B-424484.2 for an
original protest plus a reconsideration). The table’s primary key is the
first number in that string; gao_protest also matches a non-primary number
(like the .2 suffix) by searching the raw string.
Outcome is prose, not an enum (“We deny the protest.”, “We dismiss the
request for reconsideration.”, “We sustain the protest.”). It’s normalized to
denied | dismissed | sustained | other for filtering, and the raw sentence
is always kept alongside it.
Tools
- gao_protests_recent — Recently decided GAO bid-protest decisions — government-contract award disputes ruled on by the Government Accountability Office. Filter by a lookback window (days) and optionally outcome (denied|dism
- gao_protest — Fetch a single GAO bid-protest decision by its B-number (e.g. “B-424695” or “B-424484.2” for a reconsideration). Returns protester, decided_on, outcome (normalized + the raw prose sentence, e.g. “We d
- gao_protests_search — Search accumulated GAO bid-protest decisions by protester (company) name, e.g. “has
been cited in a bid protest” or “which GAO protests did Boeing file”. Case-insensitive partial match. Only - gao_protests_coverage — How much GAO bid-protest data this pack currently holds: total decisions, the earliest and most recent decided_on dates, and when the daily crawl last ran. Use this before relying on gao_protests_rece
Tools
-
gao_protest— Fetch a single GAO bid-protest decision by its B-number (e.g. B-424695 or B-424484.2 for a reconsideration). Returns protester, decided_on, outcome (normalized + the raw prose sentence, e.g. We deny t -
gao_protests_coverage— How much GAO bid-protest data this pack currently holds: total decisions, the earliest and most recent decided_on dates, and when the daily crawl last ran. Use this before relying on gao_protests_rece -
gao_protests_recent— Recently decided GAO bid-protest decisions — government-contract award disputes ruled on by the Government Accountability Office. Filter by a lookback window (days) and optionally outcome (denied|dism -
gao_protests_search— Search accumulated GAO bid-protest decisions by protester (company) name, e.g. has <company> been cited in a bid protest or which GAO protests did Boeing file . Case-insensitive partial match. Only se