White Tape · docs

Permit data for AI assistants.

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.

1. Install

pip install whitetape

Python 3.9 or newer. One dependency. The source and the MCP server are in the same package.

2. The first call

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.

The filters

FilterWhat it does
statetwo letters, MD
jurisdiction_idone area, the id from search
tradeone 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_typethe office's own type, matched loosely
issue_date_from, issue_date_toYYYY-MM-DD
value_mindollars of work, at least
sincethe cursor: 0 the first time, then the last answer's next
limitrows a call, up to 1,000

3. The fields on a row

Every county lands in the same keys. Where an office does not publish a field it is empty, never guessed.

KeyWhat it is
jurisdiction_id, jurisdictionthe issuing government: its Census id, and its name and state, the credit on the row
permit_numberthe office's own number
permit_type, permit_statusas the office names them
descriptionthe work, in the office's words
address, city, state, zipthe job site
parcel_idthe parcel, however the county keys it
contractor_name, contractor_license, contractor_phonethe business on the permit
value, feedollars
applied_date, issue_date, expire_date, final_dateYYYY-MM-DD
use_type, square_feet, units, latitude, longitudewhere published
source_file, read_on, source_extrawhere 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.

4. Paying for more than ten

The price is 7 cents a row. Two ways to pay, neither with an account.

By card, once

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_…

By wallet, per call

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.

5. The daily loop

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.

6. In Claude, with no code

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.

7. Plain HTTP, any language

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.

Limits and terms

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