Zabalist API · v1

Texas construction data API

Building permits, statewide TDLR project registrations, contractor entity resolution with live license status, 888,179 work-history links, and every Texas government solicitation — behind one REST API with a free tier.

256
Texas counties with project data
254
Counties of government bids
888,179
Contractor work-history links
142,781
Deduplicated contractor entities
Jump to a section

Getting started

Introduction

The Zabalist API is a REST API over Texas construction data. Every response is JSON, every endpoint is a GET, and every list response uses the same pagination envelope. There is no SDK to install and no OAuth dance — a key in a header is the whole integration.

What is distinctive here is not the row count. National permit vendors have more rows. What we own is the cross-source join: TDLR statewide project registrations, a deduplicated contractor entity graph, statewide government bids with SBA and HUB certification enrichment, and Travis County appraisal and deed history — resolved to the same contractor identities so a name on a permit, a party on a state filing, and a bidder on a solicitation are provably the same company.

Base URL
https://www.zabalist.com/api/public/v1

Your first call takes about thirty seconds

Create a free key, export it, and hit the permits endpoint. The Free plan includes 500 calls per month with no card on file.

Quickstart

Coverage is Texas, and it is uneven on purpose

Project registrations and government bids are statewide — 256 and 254 counties respectively. Building permits are City of Austin on a rolling two-year window, and property history is Travis County only. We publish this rather than bury it: an integration built on a coverage assumption we never stated is an integration that churns in month two.

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Getting started

Authentication

Authenticate every request with your secret key in the X-API-Key header. Keys are issued from your API dashboard.

Keys look like zbl_live_7Kq2xM…. Only the prefix is stored in readable form — the secret is hashed, so the full key is shown exactly once, at creation. If you lose it, revoke it and create another; we cannot recover it for you, and an API provider that can show you your own secret later is one that is storing it badly.

Rate limits, quotas, and Contractor Verify allowances are counted per key, not per IP. Running on shared serverless egress does not collapse you into a bucket with someone else, and issuing a key per environment or per worker is the intended pattern.

Authenticated request

Key properties

FieldTypeDescription
prefixstringFirst characters of the key, e.g. zbl_live_7Kq2xM. Shown in the dashboard and returned by /usage so you can tell keys apart.
namestringLabel you assign at creation, e.g. "production-worker". Purely for your own bookkeeping.
secretstringThe full key. Returned exactly once, at creation, and never again.
created_atstringISO 8601 timestamp the key was issued.

Never ship a key to a browser

These are secret keys with your quota and your billing attached. Call the API from your server, a serverless function, or a backend job — never from client-side JavaScript or a mobile app binary. Revoke immediately from the dashboard if a key leaks; revocation takes effect on the next request.

HTTP 401 — missing or invalid key
{
  "error": {
    "code": "invalid_api_key",
    "message": "No valid API key found. Pass your key in the X-API-Key header.",
    "docs": "https://www.zabalist.com/developers/docs"
  }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Getting started

Rate limits & quotas

Two independent limits apply. A rate limit caps sustained requests per minute, and a monthly quota caps total calls in a billing period. Both are enforced per key and both are visible at any time from the usage endpoint, which is itself free and unmetered.

Contractor Verify carries a third allowance. Verify calls are entity-resolution joins rather than row lookups, so each plan includes a number of them and additional lookups bill at $0.15 each. Every other endpoint counts only against your monthly call quota.

PlanPriceRate limitCalls / monthVerify included
FreeFree10 req/min500
Developer$49/mo60 req/min10,000500
Growth$149/mo300 req/min100,0005,000
Scale$399/mo600 req/min500,00025,000

Rate-limit response headers

FieldTypeDescription
X-RateLimit-LimitintegerRequests permitted per minute on your plan.
X-RateLimit-RemainingintegerRequests left in the current minute window.
X-RateLimit-ResetintegerUnix timestamp when the window resets.
X-Quota-RemainingintegerCalls left in the current billing period.
Retry-AfterintegerSeconds to wait. Present only on a 429.
Handling 429 with exponential backoff
HTTP 429 — rate limited
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit of 60 requests/minute exceeded for this key.",
    "docs": "https://www.zabalist.com/developers/docs"
  }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Getting started

Pagination

Every list endpoint returns the same two-key envelope: data holds the results and meta holds the cursor. Single-object endpoints such as Contractor Verify return the object at the top level instead — there is nothing to page through.

Pagination is cursor-based, not offset-based. This data is re-ingested nightly, so an offset walked over ten pages would silently skip or duplicate rows as inserts land underneath it. Pass meta.next_cursor back as the cursor parameter and stop when it comes back null.

meta object

FieldTypeDescription
countintegerNumber of records in this page’s data array.
has_morebooleanWhether more pages exist.
next_cursorstring | nullOpaque cursor for the next page. Null on the last page. Do not parse it — the encoding is not part of the contract.
Envelope
{
  "data": [ /* ...records... */ ],
  "meta": {
    "count": 25,
    "has_more": true,
    "next_cursor": "eyJpIjoicHJtXzAxSlE4RjNYSzJWTiJ9"
  }
}
Walking every page

Getting started

Errors

Errors use conventional HTTP status codes and always return the same envelope: a single error object with a stable machine-readable code and a human-readable message. Branch on the code; the message is written for a person reading a log and may be reworded.

error object

FieldTypeDescription
codestringStable machine-readable identifier. Safe to switch on.
messagestringHuman-readable explanation. Not a stable contract.
detailsobject | nullPresent on validation and unsupported-parameter errors. Keyed by the offending parameter, with the reason and the workaround.
docsstringLink to this API reference.
HTTP 400 — invalid request
{
  "error": {
    "code": "invalid_request",
    "message": "One or more query parameters are invalid.",
    "details": {
      "issued_after": "Expected an ISO date, YYYY-MM-DD"
    },
    "docs": "https://www.zabalist.com/developers/docs"
  }
}
StatusCodeWhat it means
400invalid_requestA query parameter was missing, malformed, out of range, or not supported on this endpoint. error.details names the parameter and the workaround.
401missing_api_keyNo key was sent. Supply X-API-Key (or Authorization: Bearer).
401invalid_api_keyThe key is unknown or malformed.
403key_revokedThis key was revoked. Revocation is permanent — issue a new key.
403key_disabledThis key is on a reversible hold (non-payment or abuse). Contact support.
404not_foundNo record with that id, or it is outside your plan’s coverage.
429quota_exceededMonthly call quota exhausted. Resets 00:00 UTC on the 1st. Honour Retry-After.
429rate_limitedToo many requests per minute for your plan. Honour Retry-After.
500upstream_errorSomething broke on our side. Safe to retry with backoff.
503upstream_errorA dependency is unavailable. Retry with backoff.
Branching on error.code

Endpoint

Permits

GET/api/public/v1/permits

Returns building permits sourced from the City of Austin open-data portal, joined to the deduplicated contractor entity graph so every permit carries a resolvable contractor identity rather than a raw name string.

Coverage is City of Austin on a rolling two-year window. This is a deliberate, documented limit rather than a gap we are hiding: statewide permit ingestion for DFW, Houston, and San Antonio is in progress, and this endpoint will widen without a breaking change when it lands. If you need statewide coverage today, use Projects — TDLR project registrations cover 256 Texas counties.

Query parameters

NameTypeRequiredDescription
zipstringoptionalFive-digit ZIP code, e.g. 78745. Repeatable for multiple ZIPs.
addressstringoptionalFree-text street address. Normalized and fuzzy-matched, so "1204 Cedar Bend" and "1204 CEDAR BEND DR" both resolve.
permit_typestringoptionalPermit class, e.g. building, electrical, mechanical, plumbing, driveway. Repeatable.
statusstringoptionalOne of active, final, expired, withdrawn, voided. Underwriting and prequal workflows almost always want status=final.
issued_afterstring (ISO 8601 date)optionalOnly permits issued on or after this date, e.g. 2025-01-01.
issued_beforestring (ISO 8601 date)optionalOnly permits issued on or before this date.
contractor_idstringoptionalNOT YET SUPPORTED — returns 400 with an explanatory error.details. The permits dataset carries a contractor name string and no foreign key into the entity graph, and fuzzy-guessing that link on a prequal-adjacent endpoint is not a trade-off we are willing to make silently. Use /contractors/{id}/history?record_type=permit for a contractor-scoped permit list today.
min_valuationintegeroptionalMinimum declared job valuation in whole US dollars.
limitintegeroptionalResults per page. 1–100, default 25.
cursorstringoptionalOpaque cursor from meta.next_cursor. Pass it back verbatim to fetch the next page and stop when it comes back null. Treat the token as opaque: it is positional today, so rows inserted between two page fetches can shift a row across a page boundary. For a point-in-time-consistent sweep of a changing dataset, pin the window with the date filters (e.g. issued_before) rather than relying on the cursor alone.

Response fields

FieldTypeDescription
idstringStable Zabalist permit id. Safe to store as a foreign key.
permit_numberstringIssuing jurisdiction’s permit number, verbatim.
jurisdictionstringIssuing authority, e.g. "City of Austin".
permit_typestringNormalized permit class.
work_classstringJurisdiction work description, e.g. "New", "Remodel", "Addition".
descriptionstring | nullScope of work as filed.
statusstringactive | final | expired | withdrawn | voided.
issued_datestring | nullISO 8601 date the permit was issued.
finaled_datestring | nullISO 8601 date the permit was finaled. Null until inspection closes.
valuationinteger | nullDeclared job valuation in whole US dollars.
addressobjectstreet, city, state, zip, county.
locationobject | nulllatitude and longitude, WGS 84. Null when the address could not be geocoded.
contractorobject | nullid, name, and tdlr_license of the resolved contractor entity. Pass contractor.id to Contractor History.
sourceobjectProvenance: name, url, and retrieved_at. Every row is traceable to a government source.
Request
200 — Response
{
  "data": [
    {
      "id": "prm_01JQ8F3XK2VN7RTMB4ZC9WYD5H",
      "permit_number": "2025-041882 BP",
      "jurisdiction": "City of Austin",
      "permit_type": "building",
      "work_class": "New",
      "description": "New single family residence with attached garage",
      "status": "final",
      "issued_date": "2025-03-14",
      "finaled_date": "2025-11-06",
      "valuation": 428500,
      "address": {
        "street": "1204 Cedar Bend Dr",
        "city": "Austin",
        "state": "TX",
        "zip": "78745",
        "county": "Travis"
      },
      "location": { "latitude": 30.21174, "longitude": -97.79432 },
      "contractor": {
        "id": "ent_7QK4M2XPB9F3TRWN",
        "name": "Sendero Custom Homes LLC",
        "tdlr_license": null
      },
      "source": {
        "name": "City of Austin Open Data Portal",
        "url": "https://data.austintexas.gov/d/3syk-w9eu",
        "retrieved_at": "2026-08-07T09:14:22Z"
      }
    }
  ],
  "meta": {
    "count": 25,
    "has_more": true,
    "next_cursor": "eyJ2IjoxLCJvIjoyNX0"
  }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Endpoint

Projects

GET/api/public/v1/projects

Statewide Texas project registrations from the Texas Department of Licensing and Regulation, covering commercial and accessibility-reviewed construction across 1,829 cities and 256 counties. This is the widest-coverage dataset on the API and the one to reach for when Permits is too narrow geographically.

Each project is joined to the contractor entity graph and, where the filing names them, to the owner and design firm. That join is the product: TDLR publishes the registrations, but nobody else resolves the parties in them to stable, deduplicated identities you can track over time.

Query parameters

NameTypeRequiredDescription
countystringoptionalTexas county name, e.g. Bexar. Repeatable.
citystringoptionalTexas city name, e.g. Richardson. Repeatable.
tradestringoptionalNOT YET SUPPORTED — returns 400 with an explanatory error.details. The unified project record carries a project SECTOR, not a trade; trade is an attribute of the contractor, not of the project. Filter by project_type here, or resolve the contractor via /contractors/verify and read their trade from there.
project_typestringoptionalcommercial | residential | industrial | institutional | civil.
filed_afterstring (ISO 8601 date)optionalOnly projects filed on or after this date.
filed_beforestring (ISO 8601 date)optionalOnly projects filed on or before this date.
min_valueintegeroptionalMinimum estimated project value in whole US dollars.
contractor_idstringoptionalCanonical entity id. Returns every project this contractor is attached to, in any role.
qstringoptionalFull-text search across project name, scope, and address.
limitintegeroptionalResults per page. 1–100, default 25.
cursorstringoptionalOpaque cursor from meta.next_cursor. Pass it back verbatim to fetch the next page and stop when it comes back null. Treat the token as opaque: it is positional today, so rows inserted between two page fetches can shift a row across a page boundary. For a point-in-time-consistent sweep of a changing dataset, pin the window with the date filters (e.g. issued_before) rather than relying on the cursor alone.

Response fields

FieldTypeDescription
idstringStable Zabalist project id.
registration_numberstringTDLR project registration number.
namestringProject name as filed.
project_typestringNormalized project class.
scopestring | nullScope of work as filed.
estimated_valueinteger | nullEstimated construction value in whole US dollars.
filed_datestringISO 8601 date the registration was filed.
statusstringregistered | under_review | complete.
addressobjectstreet, city, state, zip, county.
partiesarrayResolved contractor, owner, and design_firm entities with role and id. Contact fields are never included.
sourceobjectProvenance: name, url, retrieved_at.
Request
200 — Response
{
  "data": [
    {
      "id": "prj_01JQ9RB6HT4YKD2XN8VMFZ3CQW",
      "registration_number": "TABS2026-318447",
      "name": "Kami Buffet & Grill — Tenant Finish-Out",
      "project_type": "commercial",
      "scope": "Interior finish-out, 6,400 sf restaurant, new kitchen hood and grease interceptor",
      "estimated_value": 1240000,
      "filed_date": "2026-02-19",
      "status": "registered",
      "address": {
        "street": "1310 E Belt Line Rd",
        "city": "Richardson",
        "state": "TX",
        "zip": "75081",
        "county": "Dallas"
      },
      "parties": [
        {
          "role": "contractor",
          "id": "ent_4TZ8PW2QNK6MJ3RB",
          "name": "Alcorta Commercial Builders LP"
        },
        {
          "role": "design_firm",
          "id": "ent_9HM3XV7BQC2KTPFD",
          "name": "Vandiver Architects Inc"
        },
        { "role": "owner", "id": "ent_2FQ6NL8XRT4WMKBJ", "name": "Belt Line Retail Partners LLC" }
      ],
      "source": {
        "name": "Texas Department of Licensing and Regulation",
        "url": "https://www.tdlr.texas.gov/tabs/",
        "retrieved_at": "2026-08-07T09:16:41Z"
      }
    }
  ],
  "meta": { "count": 25, "has_more": true, "next_cursor": "eyJ2IjoxLCJvIjoyNX0" }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Endpoint

Contractor Verify

GET/api/public/v1/contractors/verify
Developer plan and aboveCounts against Contractor Verify allowance

The entity-resolution endpoint. Send whatever you have — a name off an invoice, a DBA, an address, a license number — and get back a canonical entity with TDLR registration status, trade, and dates, plus a confidence score and the alternates that were considered.

This is the endpoint prequalification, surety, lending, and marketplace-vetting workflows integrate against. License lookup on its own is a commodity; resolving "Chapman A/C", "CHAPMAN AIR COND & HTG", and "Chapman Air Conditioning and Heating, L.L.C." to one entity with 142,781 deduplicated peers and 888,179 work-history links behind it is not.

Calls to this endpoint count against your plan’s included Contractor Verify allowance. Every other endpoint counts only against your monthly call quota.

Query parameters

NameTypeRequiredDescription
namestringoptionalBusiness name, however messy. Required unless license or address is supplied.
licensestringoptionalA TDLR licence or registration number you already hold (format varies by trade, e.g. a TACLA/TECL-prefixed string). Matched against licence values recorded on the underlying government filings — this is NOT a lookup against the TDLR licence registry, and a miss does not mean the licence is invalid. When it matches it is the highest-precision way to resolve an entity; meta.notes flags the caveat on every licence-matched response.
addressstringoptionalStreet address to disambiguate common names.
citystringoptionalTexas city, used as a resolution tiebreaker.
tradestringoptionalExpected trade, used as a resolution tiebreaker.
min_confidencenumberoptionalReject matches below this confidence (0–1, default 0.6). Raise it for automated decisioning; lower it for human review queues.

Response fields

FieldTypeDescription
matchobject | nullThe highest-confidence resolved entity, or null when nothing cleared min_confidence.
match.confidencenumberDeterministic name-overlap score, 0–1. NOT a calibrated probability — there is no labelled entity-resolution training set behind it. Use it to rank and to threshold, not as a likelihood in an automated decision.
match.entityobjectid (UUID), slug, name, canonical_name, types[], business_type, city, county, state, zip, name_variants_merged. Store entity.id, not the name.
match.matched_onstringWhich input produced the match: name or license.
match.matched_valuestringThe exact input value that matched.
match.licensesarrayALWAYS an empty array. Zabalist holds TDLR project registrations, not the TDLR licence registry, so there is no licence status, number, or expiry to return. Do not build a licence check on this field — read match.tdlr and send the user to tdlr.registry_url.
match.tdlrobjectRegistration facts derived from TDLR project filings — registered_projects, last_registered_project_date, activity_status (active | dormant | unknown, derived from filing recency and explicitly not a licence state), license_verified (always false), license_note, registry_url.
match.work_historyobjectproject_count, verified_project_count, active_projects, total_value, first_activity_date, last_activity_date, counties_active, counties[], top_counties[], by_source{}, by_role{}.
match.govconobjectFederal enrichment: sam_registered, uei, certifications[] (SBA set-asides, e.g. HUB/8(a)), primary_naics, naics_codes[].
match.risk_flagsstring[]Derived flags: dormant, no_verified_work, name_churn. Empty array means none fired.
match.provenancestring[]Government source tokens backing this record.
match.history_urlstringReady-made path for the Contractor History endpoint.
alternatesarrayOther candidates considered, with id, name, and confidence. Use to build a human review queue.
usageobjectverify_used and verify_included for the current UTC calendar month.
data / metaobjectEvery response also carries the standard envelope. match is data[0]; meta.notes carries the licence caveat on every response.
Request
200 — Response
{
  "match": {
    "confidence": 0.94,
    "risk_flags": [],
    "licenses": [],
    "entity": {
      "id": "7f3c1a94-2d68-4e51-9b0a-6c8d5e2f4a13",
      "slug": "chapman-air-conditioning-heating-llc",
      "name": "Chapman Air Conditioning & Heating LLC",
      "canonical_name": "CHAPMAN AIR CONDITIONING & HEATING LLC",
      "types": ["contractor"],
      "business_type": "mechanical",
      "city": "Austin",
      "county": "Travis",
      "state": "TX",
      "zip": "78758",
      "name_variants_merged": 3
    },
    "matched_on": "name",
    "matched_value": "Chapman A/C and Heating",

    "tdlr": {
      "registered_projects": 47,
      "last_registered_project_date": "2026-06-11",
      "activity_status": "active",
      "license_verified": false,
      "license_note": "Zabalist does not hold the TDLR licence registry. These are registration facts derived from TDLR project filings. Verify licence status at the registry URL.",
      "registry_url": "https://www.tdlr.texas.gov/LicenseSearch/"
    },

    "work_history": {
      "project_count": 412,
      "verified_project_count": 388,
      "active_projects": 11,
      "total_value": 24875000,
      "first_activity_date": "2011-08-15",
      "last_activity_date": "2026-07-28",
      "counties_active": 4,
      "counties": ["Travis", "Williamson", "Hays", "Bastrop"],
      "top_counties": [
        { "county": "Travis", "project_count": 301, "last_project_date": "2026-07-28" },
        { "county": "Williamson", "project_count": 74, "last_project_date": "2026-05-02" }
      ],
      "by_source": { "permit": 1877, "site_plan": 12, "subdivision": 0, "tdlr": 47 },
      "by_role": { "owner": 0, "design_firm": 0, "contractor": 412, "developer": 0 }
    },

    "govcon": {
      "sam_registered": false,
      "uei": null,
      "certifications": [],
      "primary_naics": null,
      "naics_codes": []
    },

    "provenance": ["city_of_austin", "tdlr"],
    "history_url": "/api/public/v1/contractors/7f3c1a94-2d68-4e51-9b0a-6c8d5e2f4a13/history"
  },
  "alternates": [
    { "id": "b21e7d05-9a34-4c77-8e16-3f9b0d7c5218", "name": "Chapman Mechanical Services Inc", "confidence": 0.41 }
  ],
  "usage": { "verify_used": 118, "verify_included": 500 },
  "meta": {
    "count": 1,
    "limit": 1,
    "offset": 0,
    "notes": [
      "confidence is a deterministic name-overlap score, not a calibrated probability. Matches are ordered by documented project count.",
      "licenses[] is always empty: Zabalist holds TDLR project registrations, not the TDLR licence registry. Use tdlr.registry_url."
    ]
  }
}
HTTP 429 — monthly call quota exhausted
{
  "error": {
    "code": "quota_exceeded",
    "message": "Monthly quota of 500 calls exhausted for the Free plan. Resets 2026-09-01T00:00:00Z."
  }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Endpoint

Contractor History

GET/api/public/v1/contractors/{id}/history
Developer plan and above

Everything the entity graph knows about one contractor’s work, drawn from 888,179 work-history links. Returns a rolled-up summary plus the individual records, filterable by date, county, and record type.

Pair it with Contractor Verify: verify resolves who they are, history answers whether they have actually done the work. A prequalification flow typically calls verify once at onboarding and history on a schedule thereafter.

The {id} path segment is the canonical entity id returned by Contractor Verify, Permits, or Projects. Raw names are not accepted here by design — resolve first, then look up.

Query parameters

NameTypeRequiredDescription
idstring (path)requiredCanonical entity id, e.g. ent_6VN2QK9WPT3XMRBH.
record_typestringoptionalFilter to project, permit, bid, or award. Repeatable. Omit for all four.
countystringoptionalRestrict to one Texas county. Repeatable.
sincestring (ISO 8601 date)optionalOnly records on or after this date.
untilstring (ISO 8601 date)optionalOnly records on or before this date.
includestringoptionalComma-separated expansions: summary, records, counterparties. Default summary,records.
limitintegeroptionalResults per page. 1–100, default 25.
cursorstringoptionalOpaque cursor from meta.next_cursor. Pass it back verbatim to fetch the next page and stop when it comes back null. Treat the token as opaque: it is positional today, so rows inserted between two page fetches can shift a row across a page boundary. For a point-in-time-consistent sweep of a changing dataset, pin the window with the date filters (e.g. issued_before) rather than relying on the cursor alone.

Response fields

FieldTypeDescription
contractorobjectid and name of the entity the history belongs to.
summaryobjectRolled-up totals across the filtered window.
summary.total_recordsintegerRecords matching the filters.
summary.total_valueinteger | nullSum of known values in whole US dollars. Null when no record carried a value.
summary.first_activitystringISO 8601 date of the earliest record.
summary.last_activitystringISO 8601 date of the most recent record. The single best staleness signal.
summary.by_typeobjectCounts keyed by projects, permits, bids, awards.
summary.countiesstring[]Texas counties with at least one record.
summary.finaled_ratenumber | nullShare of permits reaching finaled status, 0–1. Null when no permits are in scope.
dataarrayIndividual records: type, id, date, title, value, address, role, counterparty.
metaobjectStandard pagination envelope over data.
Request
200 — Response
{
  "contractor": {
    "id": "ent_6VN2QK9WPT3XMRBH",
    "name": "Chapman Air Conditioning & Heating LLC"
  },
  "summary": {
    "total_records": 1877,
    "total_value": 41287400,
    "first_activity": "2011-08-15",
    "last_activity": "2026-07-28",
    "by_type": { "projects": 412, "permits": 1877, "bids": 23, "awards": 6 },
    "counties": ["Travis", "Williamson", "Hays", "Bastrop"],
    "finaled_rate": 0.91
  },
  "data": [
    {
      "type": "permit",
      "id": "prm_01JR2XB8MK6WTND4QVZ7FH3PYC",
      "date": "2026-07-28",
      "title": "Mechanical — replace 5-ton split system",
      "value": 14800,
      "role": "contractor",
      "address": {
        "street": "4501 Spicewood Springs Rd",
        "city": "Austin",
        "state": "TX",
        "zip": "78759",
        "county": "Travis"
      },
      "counterparty": null
    }
  ],
  "meta": { "count": 50, "has_more": true, "next_cursor": "eyJ2IjoxLCJvIjo1MH0" }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Endpoint

Bids

GET/api/public/v1/bids

Public procurement from the Texas Electronic State Business Daily and federal sources, covering 24,397 solicitations across all 254 Texas counties with 41,423 status-history transitions behind them.

The differentiator is not the bid list — it is the overlay. Every solicitation is joined to the contractor graph and to SBA 8(a) / HUBZone / WOSB certification data, which is what lets you answer "who is likely bidding against me" and "which certified sub can fill this set-aside". Those are the two questions the incumbent bid-notification services do not answer at any price.

Query parameters

NameTypeRequiredDescription
countystringoptionalTexas county name. Repeatable.
agencystringoptionalIssuing agency name or agency code.
nigp_codestringoptionalNIGP commodity class code, e.g. 909 (construction). Repeatable.
statusstringoptionalopen | closed | awarded | cancelled. Default open.
due_afterstring (ISO 8601 date)optionalOnly solicitations closing on or after this date.
due_beforestring (ISO 8601 date)optionalOnly solicitations closing on or before this date.
set_asidestringoptionalNOT YET SUPPORTED — returns 400 with an explanatory error.details. Set-aside and certification data lives on the CONTRACTOR (entity_groups.sba_certifications), not on the solicitation. Read it from /contractors/verify → match.govcon.certifications.
min_valueintegeroptionalMinimum estimated contract value in whole US dollars.
qstringoptionalFull-text search across title and description.
limitintegeroptionalResults per page. 1–100, default 25.
cursorstringoptionalOpaque cursor from meta.next_cursor. Pass it back verbatim to fetch the next page and stop when it comes back null. Treat the token as opaque: it is positional today, so rows inserted between two page fetches can shift a row across a page boundary. For a point-in-time-consistent sweep of a changing dataset, pin the window with the date filters (e.g. issued_before) rather than relying on the cursor alone.

Response fields

FieldTypeDescription
idstringStable Zabalist solicitation id.
solicitation_numberstringIssuing agency’s solicitation number, verbatim.
titlestringSolicitation title as published.
agencyobjectid, name, and type of the issuing agency.
statusstringopen | closed | awarded | cancelled.
posted_datestringISO 8601 date the solicitation was posted.
due_datestring | nullISO 8601 timestamp responses are due. Null when the agency published none.
nigp_codesstring[]NIGP commodity class codes attached to the solicitation.
countiesstring[]Texas counties in scope of the work.
estimated_valueinteger | nullEstimated contract value in whole US dollars, when published.
awardobject | nullResolved winning entity, amount, and date. Null until awarded.
documentsarrayname, url, and content_type for published attachments.
sourceobjectProvenance: name, url, retrieved_at.
Request
200 — Response
{
  "data": [
    {
      "id": "bid_01JRB4TN7QK2WMXD9VPH6FZ3CY",
      "solicitation_number": "ESBD-2026-704318",
      "title": "FM 1518 Reconstruction — Drainage and Paving, Bexar County",
      "agency": {
        "id": "agy_TXDOT",
        "name": "Texas Department of Transportation",
        "type": "state"
      },
      "status": "open",
      "posted_date": "2026-07-22",
      "due_date": "2026-09-04T14:00:00-05:00",
      "nigp_codes": ["909", "913"],
      "counties": ["Bexar"],
      "estimated_value": 8750000,
      "award": null,
      "documents": [
        {
          "name": "Invitation for Bids",
          "url": "https://www.txsmartbuy.gov/esbd/ESBD-2026-704318/ifb.pdf",
          "content_type": "application/pdf"
        }
      ],
      "source": {
        "name": "Texas Electronic State Business Daily",
        "url": "https://www.txsmartbuy.gov/esbd",
        "retrieved_at": "2026-08-08T06:02:10Z"
      }
    }
  ],
  "meta": { "count": 25, "has_more": true, "next_cursor": "eyJ2IjoxLCJvIjoyNX0" }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Endpoint

Usage

GET/api/public/v1/usage

Read your own meter. Returns consumption for the current Stripe billing period for the key making the request, plus a per-endpoint breakdown.

This endpoint is free: it never counts against your monthly call quota and never against your Contractor Verify allowance. Poll it as often as you like — the intended pattern is a check before a large batch job, and an alert when percent_used crosses a threshold you pick.

Query parameters

NameTypeRequiredDescription
periodstringoptionalcurrent (default) or previous. Only the two most recent billing periods are addressable.
breakdownbooleanoptionalInclude the per-endpoint call breakdown. Default true.

Response fields

FieldTypeDescription
planobjecttier, name, and interval of the active plan.
periodobjectstart and end of the billing period, ISO 8601.
callsobjectused, included, remaining, percent_used.
verifyobjectused, included, overage_units, overage_cost_usd at $0.15 per lookup.
rate_limitobjectrpm for the plan and the current window’s remaining allowance.
breakdownarray | nullendpoint and calls per endpoint. Null when breakdown=false.
keyobjectprefix, name, and created_at of the key that made the request. The secret is never returned.
Request
200 — Response
{
  "plan": { "tier": "growth", "name": "Growth", "interval": "month" },
  "period": { "start": "2026-08-01T00:00:00Z", "end": "2026-09-01T00:00:00Z" },
  "calls": {
    "used": 38412,
    "included": 100000,
    "remaining": 61588,
    "percent_used": 0.384
  },
  "verify": {
    "used": 5310,
    "included": 5000,
    "overage_units": 310,
    "overage_cost_usd": 46.50
  },
  "rate_limit": { "rpm": 300, "remaining": 297 },
  "breakdown": [
    { "endpoint": "/permits", "calls": 21044 },
    { "endpoint": "/projects", "calls": 11058 },
    { "endpoint": "/contractors/verify", "calls": 5310 },
    { "endpoint": "/bids", "calls": 1000 }
  ],
  "key": {
    "prefix": "zbl_live_7Kq2xM",
    "name": "production-worker",
    "created_at": "2026-05-14T18:22:03Z"
  }
}

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key

Reference

Changelog

The API is versioned in the path. Additive changes — new endpoints, new optional parameters, new response fields — ship into v1 without notice, so write clients that ignore fields they do not recognise. Anything that could break a working integration ships as v2, and v1 stays available for at least twelve months after v2 is generally available.

  1. 2026-08-08v1

    Public beta

    Permits, Projects, Contractor Verify, Contractor History, Bids, and Usage. Cursor pagination, per-key rate limiting, and the Free / Developer / Growth / Scale plan ladder.

  2. Plannedv1

    Statewide permit coverage

    DFW, Houston, and San Antonio permit ingestion widens the Permits endpoint beyond City of Austin. Additive — no client change required, and existing filters keep working.

  3. Plannedv1

    Webhooks and MCP server

    Subscribe a saved query to a webhook instead of polling, and use the same read-only endpoints from an AI agent through the Model Context Protocol.

Questions, or need something that is not here?

Email [email protected]. We answer within one business day, and we would rather hear about a missing filter than watch you work around it.

Ready to build?

Free tier: 500 calls/month, no card required.

Get your API key