TX ESBD Opportunity Search
Pricing
$6.00 / 1,000 solicitation records
TX ESBD Opportunity Search
Find Texas government contracts by keyword, NIGP code, agency or status over 59K+ solicitations. Best starting point for finding bids. AI-agent ready.
Pricing
$6.00 / 1,000 solicitation records
Rating
0.0
(0)
Developer
Wenhao Yang
Maintained by CommunityActor stats
0
Bookmarked
6
Total users
3
Monthly active users
3 days ago
Last modified
Categories
Features
Share
TX Government Contracts & Bids Search, NIGP (tx-esbd-search)
Search every Texas state & local government solicitation - the official Texas Electronic State Business Daily (ESBD) record - by keyword, NIGP commodity code, agency or status, and get back structured JSON with the NIGP codes already parsed apart.
This is the starting point of the Texas procurement family: you have a category or a set of keywords, and you want to know what the state is buying right now.
Low cost
$0.006 per record - you are charged per matching record delivered, never for the query itself. The scan stops early once it has found limit matches, so the cost tracks what you actually receive rather than the depth of the corpus.
What you get
Every match is normalized with structured NIGP codes wherever the source publishes them - not the raw "94828-Dental Services;" string. (Older solicitations sometimes carry no NIGP code at all; they are still returned, and an NIGP filter excludes them - see the note below.)
{"platform": "tx-esbd","agency": "City of Waco - M1612","agencyNumber": "M1612","title": "RFB 2026-062 District 3 Operational Street Maintenance & Pavement Rehabilitation","bidNumber": "RFB2026-062","status": "Posted","publishedDate": "11/4/2026","closeDate": "12/18/2026 2:00 PM","nigpCodes": [{ "code": "91276", "description": "Striping Streets, Parking Facilities, Lane Divisions, Etc. (Paint)" },{ "code": "91319", "description": "Construction, Curb And Gutter (Includes Maintenance, Repair, And Removal)" },{ "code": "91327", "description": "Construction, Highway And Road" },{ "code": "91345", "description": "Construction, Sewer And Storm Drain" },{ "code": "91356", "description": "Construction, Utility/Underground Projects" },{ "code": "91371", "description": "Maintenance And Repair, Highway And Road (To Include The Removal Of Asphalt, Concrete, Bitumens, Etc)" },{ "code": "91381", "description": "Maintenance And Repair, Sewer And Storm Drain (Including Removal)" },{ "code": "91396", "description": "Paving/Resurfacing, Street (Major And Residential)" },{ "code": "96861", "description": "Pavement Marking Services (Including Removal Of Markings)" },{ "code": "98852", "description": "Landscaping (Including Design, Fertilizing, Planting, Etc., But Not Grounds Maintenance Or Tree Trimming Services)" }],"url": "https://waco-texas.bonfirehub.com/portal/","internalId": "573390","created": "11/4/2026 9:27 am","lastModified": "11/4/2026 9:31 am"}
Fields: platform, agency, agencyNumber, title, bidNumber, status, publishedDate, closeDate, nigpCodes (structured), url, internalId, created, lastModified.
Statuses: Posted / Awarded / Addendum Posted / Closed / No Award / Posting Cancelled / No Award Reposted
Filters
| Filter | What it does |
|---|---|
keywords | Substring match on the title, agency, bid number and the NIGP descriptions - searching dental also finds solicitations classified under NIGP Dental Services even when the title never says "dental". |
nigpCodes | Restrict to one or more 5-digit NIGP commodity codes (exact match). A solicitation with no published NIGP code is excluded by this filter. |
agencies | Restrict to one or more agencies, matched exactly against the agency number ("529") or the full agency name as published. A partial name does not match. |
statuses | Exact, case-sensitive match on any of the lifecycle statuses above (Posted, not posted). |
publishedAfter / publishedBefore | Date window (YYYY-MM-DD) - a filter over the scanned pages, not a server-side bound. |
pages | Scan depth from the newest record (24 records per page). Default 20; the 1,500-second run budget usually covers several hundred pages on the platform's network - _meta.scannedPages reports what a run actually covered. |
limit | Hard cap: the scan stops as soon as this many matches are found. Default 50. _meta.limitReached reports it; more matches may exist beyond the scanned prefix. |
Example inputs
Everything under one NIGP commodity code - the most common way to watch a trade. nigpCodes matches the register's 5-digit commodity codes exactly; 66 of the newest 480 solicitations carried 91371 (highway maintenance & repair) when measured on 2026-11-08, so the default 20-page scan finds matches without going deep.
{ "nigpCodes": ["91371"], "limit": 50 }
Two things trip people up here: a 3-digit class (909) is not a commodity code and matches nothing - pass the 5-digit code, or use keywords (which searches NIGP descriptions too). And a tight code on a quiet week can legitimately match nothing in the newest ~500 records - raise pages (or use publishedAfter/publishedBefore) before concluding there is no such bid.
What the state is buying in a category - keywords reaches the NIGP descriptions too, so one word finds solicitations whose title never uses it.
{ "keywords": ["dental"], "limit": 50 }
Open bids from one agency:
{ "agencies": ["529"], "statuses": ["Posted"], "limit": 100 }
One specific code (e.g. painting services):
{ "nigpCodes": ["91054"], "statuses": ["Posted"], "pages": 200 }
A date window - pair a scan with publishedAfter / publishedBefore to cover a date range. Set limit too: the scan stops as soon as limit matches are in hand, so the default (50) returns only 50 records even across a wide window (max limit is 500 per run):
{ "pages": 2600, "limit": 500, "publishedAfter": "2026-01-01", "publishedBefore": "2026-03-31" }
Why this is hard
The source is a live JSON service with no server-side search: the lines filter parameter is accepted and then ignored, so every query is a client-side pass over the corpus, and the corpus is ordered by last modified, not posted date. That means a shallow scan covers the newest-modified records but can miss an older solicitation that has not been modified recently - which is why the run reports the date range it actually covered, so you can tell a genuinely empty result from a scan that stopped too early.
Scanning the full corpus
pages is scan depth from the newest record, not a page offset. The source
serves 24 records per page (~60,400 records total), one request per page, and
there is no server-side filter - so a deep scan is a long series of requests and
is bounded by the actor's run time limit (default 30 minutes, of which the
scan may use 25), not by pages alone. Two things stop a scan early: the
run-time budget and limit; both are reported in _meta (truncated /
limitReached / stopReason), so a partial answer is visible rather than
silent.
Because of that, a single run is not the way to cover the whole corpus. A filter narrows what counts as a match, but every run still scans from the newest record; a historical window deeper than the run-time budget cannot be fully covered in one run. Use a date window or NIGP code to keep the matches near the top of the scan (the source's modified-date ordering is the reason date windows beat plain deep scans):
{"pages": 800, "publishedAfter": "2026-01-01", "publishedBefore": "2026-03-31"}
{"pages": 200, "nigpCodes": ["91054"], "statuses": ["Posted"]}
The run also emits one extra _meta dataset row (not charged) reporting how
many unique records were scanned, the source's total record count, the pages
actually scanned, whether the scan is partial (truncated, set when the
run-time budget or limit stopped it), whether limit stopped it
(limitReached), why (stopReason), whether the charge cap stopped delivery
(chargeLimitReached), and the posted/modified date range the scanned prefix
covers. The prefix is ordered by last modified, not posted date, so treat
scanModDate* as the coverage signal and scanPostDate* as context.
Per-page latency depends on where the run executes; a few hundred pages per run is a comfortable working size.
Example output
{"keywords": ["painting"]} - a match, with the NIGP codes already parsed apart (measured 2026-11-05):
{"platform": "tx-esbd","agency": "Department of Public Safety","agencyNumber": "405","title": "Houston HQ 1st Floor Hallway Refresh","bidNumber": "405-27R0002724","status": "Addendum Posted","publishedDate": "10/27/2026","closeDate": "11/30/2026 5:00 PM","nigpCodes": [{"code": "91054","description": "Painting, Maintenance And Repair Services (Including Caulking)"}],"url": "https://texasdps.appianportalsgov.com/vendor-management","internalId": "571878","created": "10/27/2026 10:01 am","lastModified": "10/29/2026 11:11 am"}
A run also emits a _meta record reporting the date range the scan actually covered, so an empty result can be told apart from a scan that stopped too early.
The rest of the Texas family
One actor, one job - match your goal:
| Your goal | Actor |
|---|---|
| Find bids by keyword, NIGP, agency, status | tx-esbd-search - you are here |
| Check one specific bid by its number | tx-esbd-get |
| Monitor new & changed solicitations on a schedule | tx-esbd-changes |
| Who won state contracts, at what amounts | tx-esbd-awards |
| Find / vet a vendor - HUB, SDV, small business, NIGP capability | tx-cmbl-vendors |
| Find disabled-veteran (DV) HUB vendors | tx-hub-certified |
| Statewide contracts - who's on each SPD/TxSmartBuy contract | tx-smartbuy-contracts |
| Pull the raw full corpus (query + monitor) - power users | sled-tx-procurement |
Notes
- Data source: Texas Comptroller - ESBD. Public state records.
- No login, no anti-bot - uses the official public JSON API.
- Full corpus - ~60,400 solicitations (posted, awarded, closed & historical). For a complete historical sweep in one product, use
sled-tx-procurement(raw corpus + monitor). - NIGP coverage varies by age - newer solicitations almost all carry NIGP codes; a 2019 slice measured on 2026-11-08 had them on 62% of rows (2020: 82%). An
nigpCodesfilter excludes a record that publishes no code, whilekeywordsand the other filters still return it. - Empty arrays mean "not supplied" -
{"statuses": []}is not a filter that matches zero rows; it scans unfiltered. Omit a key instead of passing an empty list. - A source error mid-scan fails the run rather than delivering a partial answer (the scan stops cleanly only at the run-time budget or
limit); retry or split the query. - Also available as a hosted MCP tool (
j0401--tx-esbd-search) for AI assistants.
Related actors
Also available in this account:
- TX Procurement Change Monitor - new and changed solicitations since your last run.
- TX Contract Awards - who won, for how much, with HUB status and VPTS grades.
- TX Vendor Directory (CMBL) - the supplier-side register with VID-level performance.
- TX Business Entities - the franchise-tax / SOS register for those vendors.
License
MIT - see ./LICENSE.
Disclaimer
This product is an independent data service and is not affiliated with, endorsed by, or sponsored by the State of Texas or any government agency. All solicitation data is public government information. We recommend verifying any opportunity on the official portal before acting.