API docs
The Townline data API is JSON over HTTPS. Each dataset is a feed with a cursor: your first pull returns everything, and every pull after that returns only what’s new or changed.
Get started
Once your account is active, sign in at townline.app/a/signin with the email address on your account. Open Settings and make a key under API keys. A key is shown once, when you make it, so copy it then. You can have up to 10 keys, and deleting one turns it off right away.
Send the key in the x-api-key header with every request to https://api.townline.app. Keep it on your servers: anyone with the key can pull your data.
curl -H "x-api-key: $TOWNLINE_KEY" \ "https://api.townline.app/v1/restaurants?limit=1" curl -H "x-api-key: $TOWNLINE_KEY" \ "https://api.townline.app/v1/permit-events?limit=1"
Pull the restaurant feed
GET /v1/restaurants returns new restaurants, bars, cafés and bakeries in the counties your key covers, from their first filing, one record per place.
sinceSeqWhere to start: the nextSeq your last call returned. Leave it out, or send 0, to start from the beginning of the feed.
limitHow many records a call returns, 1 to 1,000. The default is 200.
regionIdOnly these counties: one or more ids from GET /v1/regions, separated by commas. Leave it out for every county your key covers.
Each call returns a page:
{
"data": [ { "venueId": "v_3f9c2a7e01b44d58c6a1", … } ],
"nextSeq": "48733",
"hasMore": true
}Save nextSeq and call again with sinceSeq=nextSeq while hasMore is true. Don’t stop on a short or empty page: a page can hold fewer places than limit, even none, and still move the cursor. When hasMore is false, you’re caught up. Next time, start from the nextSeq you saved.
New records arrive every day, so pulling daily or weekly works. The feed holds back places that changed in the last 10 minutes, so it never moves past one that’s still being written; those come on your next pull.
These scripts pull everything new or changed since the last run. Set TOWNLINE_KEY to your key first.
#!/usr/bin/env bash
# Appends every place that's new or changed since the last run to
# places.jsonl, and keeps the cursor in cursor.txt. Needs curl and jq.
set -euo pipefail
seq=$(cat cursor.txt 2>/dev/null || echo 0)
while :; do
page=$(curl -sf -H "x-api-key: $TOWNLINE_KEY" \
"https://api.townline.app/v1/restaurants?sinceSeq=$seq&limit=1000")
echo "$page" | jq -c '.data[]' >> places.jsonl
seq=$(echo "$page" | jq -r '.nextSeq')
echo "$seq" > cursor.txt
[ "$(echo "$page" | jq -r '.hasMore')" = true ] || break
doneimport os
import requests
KEY = os.environ["TOWNLINE_KEY"]
URL = "https://api.townline.app/v1/restaurants"
places = {} # venueId -> place. Load the copy you saved last time.
seq = "0" # Load the nextSeq you saved last time.
while True:
response = requests.get(
URL,
headers={"x-api-key": KEY},
params={"sinceSeq": seq, "limit": 1000},
timeout=60,
)
response.raise_for_status()
page = response.json()
for place in page["data"]:
for old_id in place["replaces"]:
places.pop(old_id, None)
places[place["venueId"]] = place
seq = page["nextSeq"] # Save it for next time.
if not page["hasMore"]:
break
print(len(places), "places; the next pull starts at", seq)// Node 18 or later.
const KEY = process.env.TOWNLINE_KEY;
const places = new Map(); // venueId -> place. Load the copy you saved last time.
let seq = "0"; // Load the nextSeq you saved last time.
for (;;) {
const url = `https://api.townline.app/v1/restaurants?sinceSeq=${seq}&limit=1000`;
const response = await fetch(url, { headers: { "x-api-key": KEY } });
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const page = await response.json();
for (const place of page.data) {
for (const oldId of place.replaces) places.delete(oldId);
places.set(place.venueId, place);
}
seq = page.nextSeq; // Save it for next time.
if (!page.hasMore) break;
}
console.log(places.size, "places; the next pull starts at", seq);Keep your copy in sync
A place comes back whole whenever any of its records changes. Store places by venueId and overwrite the one you have. The same place always keeps the same id.
Sometimes two places we sent turn out to be one business. The place that stays lists the other ids in replaces; delete those from your copy.
seq is the place’s latest position in the feed and changedAt is when it last changed.
The restaurant record
An example record. The values are made up.
{
"venueId": "v_3f9c2a7e01b44d58c6a1",
"replaces": [],
"seq": "48733",
"changedAt": "2026-10-28T06:12:09.114Z",
"regionId": "queens-ny",
"name": "Golden Wok Kitchen",
"operator": "GOLDEN WOK KITCHEN LLC",
"cuisine": "Chinese",
"licenseType": null,
"address": "41-12 Main St",
"city": "Flushing",
"zip": "11354",
"latitude": 40.7571,
"longitude": -73.8297,
"bbl": "4012340056",
"stage": "health_permit_issued",
"stageDate": "2026-08-09",
"operating": false,
"firstSignalDate": "2026-05-14",
"filings": [
{
"kind": "health_permit",
"publisher": "Health department",
"businessName": "GOLDEN WOK KITCHEN",
"operatorName": "GOLDEN WOK KITCHEN LLC",
"category": "Chinese",
"status": "Permit issued",
"address": "41-12 MAIN ST, QUEENS, NY 11354",
"filedDate": "2026-07-02",
"issuedDate": "2026-08-09",
"firstInspectionDate": null,
"firstSeenAt": "2026-07-03T05:40:11.000Z",
"presentAtLaunch": false,
"removedAt": null
}
],
"permits": [
{
"id": "p_8d21c0b9e47f3a6c5b10",
"issuedDate": "2026-05-14",
"permitType": "Alteration",
"description": "Install kitchen exhaust hood and duct.",
"status": "Issued",
"valuation": 85000,
"contractor": "EXAMPLE MECHANICAL CORP",
"address": "41-12 MAIN STREET",
"matchMethod": "bbl"
}
],
"owners": [
{ "name": "RIVERA, PAT", "role": "Permit operator", "publisher": "Health department" }
],
"previousAtAddress": [
{
"name": "MAIN ST NOODLE HOUSE",
"category": "Chinese",
"firstInspectionDate": "2019-03-11",
"lastInspectionDate": "2025-12-02",
"publisher": "Health department"
}
]
}The place
venueIdstringStable id of this business at this address. Upsert on it.
replacesarray of stringsVenue ids served earlier that turned out to be this business. Delete them.
seqstringThis venue's latest position in the feed.
changedAtstringWhen this place last changed, as an ISO 8601 timestamp.
regionIdstringThe county the place is in, as an id from GET /v1/regions (kings-ny, bergen-nj, fairfield-ct, ...).
namestring or nullTrade name: a liquor record's DBA, else the health permit's name; the legal entity only when nothing else names the place (a company registration carries the company name only).
operatorstring or nullThe company behind the place: the licensee on a liquor application or license, else the company a health permit names, else the company registered for it (in NYC, a company registered at the place's street address under a name sharing a word with the place's), else a health permit's name when that is a company (…LLC, …INC). null when no record names one.
cuisinestring or nullCuisine as the health department records it at the first inspection; NYC only.
licenseTypestring or nullLiquor license class (Restaurant, Food & Beverage Business, Cafe Liquor, ...).
addressstring or nullStreet address.
citystring or nullTown or city.
zipstring or nullZIP code.
latitudenumber or nullLatitude (WGS 84). null when the address could not be placed on the map.
longitudenumber or nullLongitude (WGS 84). null when the address could not be placed on the map.
bblstring or nullNYC tax lot (10 digits), in the five boroughs.
stagestringThe furthest step any record shows. See Stages below.
stageDatestring or nullThe date of the step in stage, YYYY-MM-DD. Which date it is depends on the stage (see Stages below).
operatingboolean or nulltrue once a record shows the place serving; false while a record shows it not yet open; null when no record says.
firstSignalDatestring or nullEarliest date any record or linked permit shows, YYYY-MM-DD: how early the business was visible.
filingsarrayThe public records that name this business.
permitsarrayFood-service building permits Townline holds for the property (the build-out, kitchen hood, sign), from 18 months before a record to 9 months after. A permit on a property shared with other new businesses appears only on the one it can be attributed to.
phonestring or null (contacts add-on)The business phone on the place's health permit (NYC only for now), as +1XXXXXXXXXX; null when no record lists one. Present only for keys with the contacts add-on.
ownersarrayThe people behind the business, each once: those its own records name first (a license or permit held in a person's own name, a permit's operator, the owner on an inspection report), then those the state company register lists for its company (its chief executive, the person who receives its legal papers, its members and officers). People only, never a company, a registered agent or a law firm, and never a home address. Empty when no record or register names one.
previousAtAddressarrayWhat was at this street address before: food businesses the health department inspected there, last inspected before this business first showed (a company registered for it earlier doesn't count), other than this business. Newest first, at most two. NYC only for now; empty where none is listed, and where the address holds more than two other food businesses (a mall, food hall or office tower), since the address can't say which space this one took. Places drop off the health department's list some time after they close, so this reaches back a few years at most.
Stages
stage is the furthest step any record shows, and stageDate is the date of that step.
liquor_application_pendingliquor_application_conditionally_approvedliquor_application_closed — Left the pending list.liquor_license_pendingliquor_license_provisional — Final inspection due.liquor_license_activeliquor_license_inactivefood_license_pending — stageDate is the preliminary approval.food_license_activefood_license_inactivehealth_permit_pending — Health permit applied for, not yet issued; stageDate is the application date.health_permit_issued — Permitted, not yet inspected.inspected — First health inspection done.building_permit_issued — A building permit names the place being fitted out or taking over the space, before any other record; stageDate is that permit's issue date.business_registered — Only a company registration so far; stageDate is the registration date.business_registration_inactive — The company was dissolved.The records behind it
Each entry in filings is a public record that names the place.
filings[].kindstringWhat the record is. See Record kinds below.
filings[].publisherstringThe kind of agency the record comes from: State liquor authority, Health department, Licensing agency, State company register or Building department.
filings[].businessNamestring or nullTrade name (DBA) as the record gives it.
filings[].operatorNamestring or nullThe legal name the record gives for who runs the place: the licensee, the permit holder or the registered company.
filings[].categorystring or nullLicense class, establishment type, industry or cuisine, as the record gives it (Restaurant Liquor, Bakery, Delicatessen, Full-Service Restaurants, ...).
filings[].statusstring or nullThe status as the record gives it.
filings[].addressstring or nullThe address as the record gives it.
filings[].filedDatestring or nullApplication received, license preliminarily approved, or company registered, YYYY-MM-DD.
filings[].issuedDatestring or nullLicense or permit issued, finally approved, in effect or changed, YYYY-MM-DD; for a building_permit, the first permit naming the place. null while an application is pending, and for license lists that carry no dates: a record that is not presentAtLaunch is then dated by firstSeenAt.
filings[].firstInspectionDatestring or nullFirst health inspection on record, YYYY-MM-DD: for a place this new, usually the pre-opening one (some agencies list only their latest inspection, or don't say whether it came before opening). null = not yet inspected.
filings[].firstSeenAtstringWhen Townline first read this record.
filings[].presentAtLaunchbooleanAlready on the agency's list when Townline started reading it, so firstSeenAt is not when it appeared.
filings[].removedAtstring or nullWhen the record left the agency's list. A liquor application leaves the pending list when it is approved or withdrawn; the agency does not say which.
Record kinds
liquor_application — A liquor license application.liquor_license — A liquor license issued, transferred, moved or back in use.health_permit — A health department food permit, from application through the first inspection.food_license — A food license with no liquor in it: a bakery, food store, deli or restaurant license.business_registration — A company registered for the place, weeks before any license.building_permit — A building permit that names the place being fitted out or taking over the space, often weeks to months before any other record. Townline keeps the name it gives, never its text.Permits
permits[].idstringStable id of this permit, the same one the permit feeds use.
permits[].issuedDatestring or nullThe permit's date, YYYY-MM-DD: the date it was issued or, in some towns, the date it was applied for.
permits[].permitTypestring or nullThe type of permit, as the building department gives it.
permits[].descriptionstring or nullThe work description, with phone numbers and email addresses replaced by [phone removed] and [email removed].
permits[].statusstring or nullThe permit status, as the building department gives it.
permits[].valuationnumber or nullThe declared cost of the work in US dollars. null when the permit gives none.
permits[].contractorstring or nullThe contractor named on the permit, with any phone number or email address cut out.
permits[].addressstring or nullThe address as the permit gives it.
permits[].matchMethodstringbbl (same NYC tax lot), address (same street address and town), or nearby (same street address within ~150 m, town names differing).
People
owners[].namestringAs the record or register prints it: "RIVERA, PAT", "Pat Rivera".
owners[].rolestringWhat the record or register calls them: Licensee (a license held in a person's own name), Permit operator, Owner or Business owner (on an inspection report), Permit holder, Chief executive, Receives legal papers (the person a company names to receive its legal papers: for a small company usually an owner, sometimes its lawyer or accountant), or a company officer's title as the register gives it (Member, Manager, Officer, Director; Principal when it gives none).
owners[].publisherstringWhere the name comes from: the kind of agency whose record names them, or State company register.
What was there before
previousAtAddress[].namestring or nullIts name, as the health department lists it.
previousAtAddress[].categorystring or nullThe health department's cuisine (Mexican, Coffee/Tea, ...).
previousAtAddress[].firstInspectionDatestring or nullIts earliest inspection on the health department's list, YYYY-MM-DD.
previousAtAddress[].lastInspectionDatestring or nullIts latest inspection, YYYY-MM-DD: about when it was last open.
previousAtAddress[].publisherstringHealth department.
Pull the permit feed
GET /v1/permit-events returns building permits in the counties your key covers, each one once, in the order we first found them. Home jobs come in it too: see Home jobs.
sinceSeqWhere to start: the nextSeq your last call returned. Leave it out, or send 0, to start from the beginning of the feed.
limitHow many records a call returns, 1 to 1,000. The default is 500.
regionIdOnly these counties: one or more ids from GET /v1/regions, separated by commas. Leave it out for every county your key covers.
Pages work as in the restaurant feed: save nextSeq, call again with sinceSeq=nextSeq while hasMore is true, and don’t stop on a short or empty page. Starting from 0 walks every permit we hold, so a first pull takes many pages.
Each permit comes as it stands when you pull it. What changes on it later comes through the change feed, never as a new row here. The feed holds back permits we found in the last 10 minutes; those come on your next pull.
Permit ids start with p_ and match the ids in a restaurant’s permits. This script keeps a copy of your permits up to date from both feeds, the permit feed first.
import os
import requests
KEY = os.environ["TOWNLINE_KEY"]
API = "https://api.townline.app/v1"
permits = {} # id -> permit. Load the copy you saved last time.
cursors = {"permit-events": "0", "permit-changes": "0"} # Load the ones you saved.
def pull(feed):
while True:
response = requests.get(
f"{API}/{feed}",
headers={"x-api-key": KEY},
params={"sinceSeq": cursors[feed], "limit": 1000},
timeout=60,
)
response.raise_for_status()
page = response.json()
yield from page["data"]
cursors[feed] = page["nextSeq"] # Save it for next time.
if not page["hasMore"]:
break
for permit in pull("permit-events"):
permits[permit["id"]] = permit
for change in pull("permit-changes"):
if change["event"] == "deleted":
permits.pop(change["id"], None)
continue
permit = permits.get(change["id"])
if permit is None:
continue # Not pulled yet: the permit feed will bring it.
for field, value in change["changes"].items():
if value["after"] is None:
permit.pop(field, None)
else:
permit[field] = value["after"]
print(len(permits), "permits")The permit record
An example record. The values are made up. A field with no value is left out: permit records never send null.
{
"id": "p_4b7e19c2d05a8f3e6c21",
"seq": "1048213",
"regionId": "westchester-ny",
"muni": "Lewisboro",
"permitType": "Solar",
"description": "Install 7.6 kW roof-mounted solar array, 19 panels.",
"status": "Issued",
"address": "100 Example Rd",
"blockLot": "1-2-3",
"issuedDate": "2026-10-27",
"valuation": 32000,
"contractor": "EXAMPLE SOLAR LLC",
"contractorLicense": {
"credential": "HOME IMPROVEMENT CONTRACTOR LICENSE",
"licenseNo": "000000",
"status": "Active",
"active": true,
"expirationDate": "2027-06-30"
},
"ownerName": "LEE, JORDAN",
"ownerAddress": "100 EXAMPLE RD, SOUTH SALEM, NY 10590",
"ownerAbsentee": false,
"latitude": 41.2551,
"longitude": -73.5903,
"firstSeenAt": "2026-10-20T05:12:44.000Z",
"statusChangedAt": "2026-10-28T05:40:02.000Z",
"changedAt": "2026-10-28T05:40:02.000Z",
"statusEvents": [
{
"id": "e_9a03f6b1c87d42e5a0b3",
"oldStatus": "Under review",
"newStatus": "Issued",
"observedAt": "2026-10-28T05:40:02.000Z"
}
],
"trades": [{ "id": "solar", "name": "Solar", "confidence": 0.9 }]
}idstringStable id of this permit: p_ and 20 characters. Store permits by it. The change feed, search and GET /v1/permits/{id} use the same id.
seqstringThis permit's position in the permit feed, as a string of digits. It never changes. Compare seq values as numbers, not as text.
regionIdstringThe county the permit is in, as an id from GET /v1/regions (westchester-ny, kings-ny, fairfield-ct, ...).
munistringThe city, town, village or borough the work is in.
permitTypestringThe type of permit, as the building department gives it (Building, Electrical, Plumbing, ...).
descriptionstringThe work description, as the building department gives it. Phone numbers and email addresses in it are replaced with [phone removed] and [email removed].
statusstringThe permit's current status, as the building department gives it. The wording varies from town to town.
addressstringThe street address of the work, written the way the town writes it. When the permit gives none, we fill it in from the description or from property records where we can.
blockLotstringThe property's tax block and lot, or parcel number, as the permit gives it. The format varies by area.
issuedDatestringThe permit's date, YYYY-MM-DD. Depending on the town, it's the date the permit was issued or the date it was applied for.
valuationnumberThe declared cost of the work in US dollars, as the permit gives it.
contractorstringThe contractor named on the permit, with any phone number or email address cut out.
applicantNamestringWho applied for the permit: often the contractor, but not always. Any phone number or email address is cut out.
ownerNamestringThe property owner as the permit names them or, when it names none, the owner of record from property tax records. Any phone number or email address is cut out.
ownerAddressstringThe owner's mailing address.
ownerAbsenteebooleantrue when the owner's mailing address is a different street address from the property's, false when it's the same. Left out when either one is missing.
contractorLicenseobjectThe contractor license that best matches the contractor's name, from license lists in the same state: an active license first, then the latest expiry. See Licenses below.
applicantLicenseobjectThe same for the applicant's name.
latitudenumberLatitude of the work site (WGS 84): from the permit when it gives one, otherwise from the address or parcel.
longitudenumberLongitude of the work site (WGS 84).
firstSeenAtstringWhen we first found this permit, as an ISO 8601 timestamp.
statusChangedAtstringWhen we last saw the status change, as an ISO 8601 timestamp. Left out until we see one.
changedAtstringWhen a field the change feed reports last changed on this permit, as an ISO 8601 timestamp. Left out if none has. It can be earlier than the change feed, which starts on October 28, 2026.
statusEventsarrayEach status change we've seen on this permit, oldest first; empty if none. See Status history below.
tradesarrayOur trade tags for the work, from the permit's type and description: up to 5, most confident first. See Trades below.
Licenses
contractorLicense and applicantLicense have the same fields. They’re matched by name.
contractorLicense.credentialstringWhat the license is, as the license list names it (a home improvement or electrical contractor license, for example).
contractorLicense.licenseNostringThe license number.
contractorLicense.statusstringThe license's status as the license list gives it (Active, Expired, ...).
contractorLicense.activebooleantrue when the license was active in our latest copy of the license list.
contractorLicense.expirationDatestringWhen the license expires, YYYY-MM-DD.
Status history
statusEvents[].idstringStable id of this status change: e_ and 20 characters.
statusEvents[].oldStatusstringThe status before the change. Left out when it was blank.
statusEvents[].newStatusstringThe status after the change. Left out when it became blank.
statusEvents[].observedAtstringWhen we saw the change, as an ISO 8601 timestamp. This can be a few days after the town made it.
Trades
GET /v1/trades lists every trade id and name. It needs no key.
trades[].idstringTrade id from GET /v1/trades: solar, roofing-siding-gutters, hvac-heating-cooling, electrical, plumbing-piping, pool-installation-service, ...
trades[].namestringTrade name for display: Solar, Roofing / siding / gutters, ...
trades[].confidencenumber0.9 when the permit type or its fee breakdown names the trade, 0.75 when only the description does. 0.5 marks the general-contractor tag, given when nothing else matched.
Permit changes
GET /v1/permit-changes sends what changed on a permit after the permit feed sent it: each changed field, before and after, in the order we saw the changes. It takes the same parameters as the permit feed but has its own cursor, so save its nextSeq separately. It holds back changes from the last 30 minutes, and it starts on October 28, 2026.
{
"id": "p_4b7e19c2d05a8f3e6c21",
"seq": "88412",
"regionId": "westchester-ny",
"event": "changed",
"observedAt": "2026-10-28T05:40:02.000Z",
"changes": {
"status": { "before": "Under review", "after": "Issued" },
"issuedDate": { "before": null, "after": "2026-10-27" }
}
}idstringThe permit's id (p_...), the same one the permit feed gives it.
seqstringThis change's position in the change feed. It's this feed's own cursor, not the permit's seq.
regionIdstringThe county the permit is in.
eventstringchanged, or deleted when the permit was taken down. A deleted event has no changes.
observedAtstringWhen we recorded the change, as an ISO 8601 timestamp.
changesobjectEach changed field under its name on the permit record, as {"before": ..., "after": ...}. null means the field was empty before, or was cleared.
These fields can change: muni, permitType, description, status, address, blockLot, issuedDate, valuation, contractor, applicantName, ownerName, ownerAddress, latitude, longitude. Their values are cleaned the same way as on the permit record. Other fields, such as statusEvents and trades, don’t come as changes: read the permit again with GET /v1/permits/{id} to refresh them.
Right after we find a permit, we fill in what it lacks, such as its coordinates or its owner. Those fill-ins come as changes with before set to null, so some repeat values your copy already has. A change can name a permit you haven’t pulled yet; the permit feed will bring it.
Search permits
GET /v1/permits finds permits by county, trade, property type, date, town, status or words. Use it to pull a slice, such as a year of solar jobs, before you follow the feeds.
regionIdOnly these counties: ids from GET /v1/regions, separated by commas. Leave it out for every county your key covers.
tradeIdOnly permits tagged with one of these trades: ids from GET /v1/trades, separated by commas.
propertyTypesingle-family, multi-family, commercial or other, from property records. A permit whose property type we don't know matches none of them.
issuedAfter, issuedBeforeOnly permits whose issuedDate is on or after, or on or before, this date (YYYY-MM-DD). Permits with no date are left out.
muniOnly towns whose name contains this, such as Yonkers.
statusactive (issued, work under way), review (applied for or in review), stopped (expired, stop-work order, on hold, void or revoked) or closed (finished or signed off). Any other value has to match the status exactly as the town gives it.
qWords that must all appear in the address, description, names, town or permit type.
sortnewest, by issuedDate (the default), or valuation, by declared cost.
orderdesc (the default) or asc.
limitHow many permits a call returns, 1 to 1,000. The default is 50.
cursorThe nextCursor your last page returned.
curl -H "x-api-key: $TOWNLINE_KEY" \ "https://api.townline.app/v1/permits?tradeId=solar&propertyType=single-family&issuedAfter=2026-10-01&limit=1000"
{
"data": [ { "id": "p_4b7e19c2d05a8f3e6c21", … } ],
"total": 1834,
"nextCursor": "…"
}Call again with cursor=nextCursor until a page comes back without one. total counts every match. The records are the permit feed’s, seq included, so after a backfill you can start the permit feed from the highest seq you got.
One permit in full
GET /v1/permits/{id} returns one permit with four more fields than the feed and search carry.
propertyContextobjectFacts about the property from assessor records, or from other property data where there's no assessor record. Which fields are filled varies by area.
contractorActivityobjectThe contractor's permits over the last 365 days, across every permit we hold. Given when the name matched a license or is clearly a business.
applicantActivityobjectThe same for the applicant.
propertyPermitsarrayUp to 10 other permits at the same property, newest first. In New York City, the other permits on the same building job.
The property
propertyContext.propertyTypestringThe property's use class, as the area's records word it (Single family, Two family, Apartment building, ...).
propertyContext.multiUnitbooleantrue when the property houses more than one household. Left out otherwise.
propertyContext.yearBuiltnumberThe year the building was built.
propertyContext.livingAreanumberThe building's floor area in square feet.
propertyContext.appraisedValuenumberThe property's market value in US dollars from assessor records, estimated from the assessed value in some areas.
propertyContext.lastSalePricenumberThe last recorded sale price in US dollars, where the records carry sales.
propertyContext.lastSaleDatestringThe date of the last recorded sale, YYYY-MM-DD.
The contractor’s year
applicantActivity has the same fields as contractorActivity.
contractorActivity.permitCountnumberHow many permits dated in the last 365 days name this business as applicant or contractor.
contractorActivity.muniCountnumberHow many different towns those permits are in.
contractorActivity.totalValuationnumberTheir total declared cost in US dollars. Left out when none of them gives one.
contractorActivity.windowDaysnumberThe length of the window in days: 365.
Other permits at the property
propertyPermits[].idstringThat permit's id (p_...).
propertyPermits[].permitTypestringIts type of permit.
propertyPermits[].leadTradestringThe name of its most confident trade tag.
propertyPermits[].statusstringIts current status.
propertyPermits[].issuedDatestringIts date, YYYY-MM-DD, with the same meaning as issuedDate.
propertyPermits[].valuationnumberIts declared cost of work in US dollars.
propertyPermits[].sameJobbooleantrue when it's dated within 45 days of this permit, which we count as the same job.
Home jobs
Solar, roofing, HVAC, electrical, plumbing and pool jobs on houses are building permits. They come in the permit feed with every other permit, with the same key.
To pick out a kind of work, filter on trades[].id. To check that a permit is on a house, read it with GET /v1/permits/{id}: propertyContext gives the property’s use class, year built, size and value. Search does both in one call, with tradeId and propertyType=single-family, as in the example above.
Regions
GET /v1/regions lists the county ids, such as kings-ny, bergen-nj and fairfield-ct. It needs no key. Every record carries its county in regionId.
A key covers every county or a list of them. Asking for a county outside your key returns 403.
Errors
400A parameter isn't valid, such as a limit over 1,000.
401No key, a key we don't know, or a deleted key.
403The key doesn't include this feed or county, or your account is off.
404GET /v1/permits/{id} with an id we don't have.
5xxSomething failed on our side. Wait a minute and try the same call again.
Every error comes with a message saying what went wrong.
Help
Questions, or need another county or dataset? Email [email protected]. A real person reads it.