Every government-issued building permit we hold, in one schema, from one address. No account, no key, no sign-up. The first ten rows a month are free with nothing asked. After that, 7 cents a row, paid by your wallet inside the call.
pip install whitetape
Python 3.9 or newer. One dependency. The source and the MCP server are in the same package.
from whitetape import search, count, permits
search("annapolis") # free: the areas we hold that match, with counts
count(state="MD", trade="roofing") # free: how many rows, and the price for all of them
rows = permits(state="MD", trade="roofing", since=0) # the rows; the first ten are free
for r in rows["rows"]:
print(r["issue_date"], r["address"], r["contractor_name"], r["value"])
Or from the command line:
whitetape search annapolis whitetape count --state MD --trade roofing whitetape rows --state MD --trade roofing --since 0
Search never returns a row and never costs anything. Count prices a filter before you pull it. Permits returns the rows plus a cursor, next, for the daily loop below.
| Filter | What it does |
|---|---|
state | two letters, MD |
jurisdiction_id | one area, the id from search |
trade | one word over every city's own permit types: roofing, hvac, plumbing, electrical, solar, pool, fence, demolition, new_construction, remodel, windows_doors, garage_adu, fire |
permit_type | the office's own type, matched loosely |
issue_date_from, issue_date_to | YYYY-MM-DD |
value_min | dollars of work, at least |
since | the cursor: 0 the first time, then the last answer's next |
limit | rows a call, up to 1,000 |
Every county lands in the same keys. Where an office does not publish a field it is empty, never guessed.
| Key | What it is |
|---|---|
jurisdiction_id, jurisdiction | the issuing government: its Census id, and its name and state, the credit on the row |
permit_number | the office's own number |
permit_type, permit_status | as the office names them |
description | the work, in the office's words |
address, city, state, zip | the job site |
parcel_id | the parcel, however the county keys it |
contractor_name, contractor_license, contractor_phone | the business on the permit |
value, fee | dollars |
applied_date, issue_date, expire_date, final_date | YYYY-MM-DD |
use_type, square_feet, units, latitude, longitude | where published |
source_file, read_on, source_extra | where the row came from, when, and any county column that matched nothing, kept as it came |
No homeowner is on a row. The owner's and applicant's names are stripped before a row leaves, in every state. What you get is the permit, the property, and the contractor. The full schema with the condense rule: /schema.json.
The price is 7 cents a row. Two ways to pay, neither with an account.
Buy a prepaid link at whitetape.io/credit/: $20 is 400 rows, $100 is 3,333. The link is the balance. Put its token in the environment and every call debits it.
export WHITETAPE_CREDIT=wt_…
USDC on Base from your own wallet, signed inside the call.
pip install "whitetape[pay]" export WHITETAPE_WALLET_KEY=0x… # the private key of a Base wallet holding USDC export WHITETAPE_MAX_USD=5 # the most one call may spend; default 5
Then the same calls as before. When a call costs money, the library signs the payment, resends the call with it attached, and the rows come back with the receipt. One call, no account. count first if you want the exact price before you pull.
The wallet key stays on your machine. It is never sent to us; only the signed payment is. Keep it in an environment variable, not in code.
Fresh permits are the point. Pull once a day with the cursor and no row comes twice.
from whitetape import rows_since
cursor = load_cursor() # 0 the first time; keep it between runs
for row, cursor in rows_since(state="MD", trade="roofing", cursor=cursor):
handle(row) # your CRM, your sheet, your agent
save_cursor(cursor)
The cursor is a bookmark on our load order, so a county that posts late still reaches you the day it lands with us. A daily pull for one trade in one state is usually a few dozen rows: pennies.
The package is also an MCP server. Add it once and Claude can search, count, and pull permits by itself.
pip install "whitetape[mcp]"
{"mcpServers": {"whitetape": {"command": "whitetape-mcp"}}}
Or hosted, nothing installed: the same three tools at https://api.whitetape.io/mcp (Streamable HTTP). Add it as a remote MCP server or a Claude connector by URL. Paid rows come off a prepaid link sent as Authorization: Bearer wt_...; without it the free rows answer. It is also listed on the MCP registry as io.github.JonathanTECH1217/whitetape.
Three tools: search, count, permits. Add WHITETAPE_WALLET_KEY to its env for paid rows. Any other agent reads /llms.txt and finds the same door.
GET https://api.whitetape.io/permits/search?q=annapolis&state=MD GET https://api.whitetape.io/permits?state=MD&trade=roofing&since=0&limit=100 GET https://api.whitetape.io/permits?state=MD&trade=roofing&count_only=1
Past the free ten the answer is 402 Payment Required with the price and where to pay. The full exchange, for a program with no library: the 402 door. Machine-readable terms: /.well-known/x402.json.
120 calls a minute from one address. Rows are public records and may be stored and resold. White Tape data is not a consumer report and may not be used, in whole or in part, to decide a person's eligibility for credit, insurance, employment, housing, or any other purpose covered by the Fair Credit Reporting Act. Only areas we hold are served; search lists them. Questions: hello@whitetape.io.
Built and used by Monarc Build for its home-services clients. whitetape.io · in plain words