DataJackpot API
One REST API for US property data: 45.5M+ building permits and certificates of occupancy, 80.5M assessor parcels, and 3.3M licensed contractors. Refreshed continuously, and sourced in part directly from town clerks, so it includes records that are not posted online anywhere else.
Quick start
- Subscribe to an API plan on /pricing (from $179/month), then generate your key on your account page. Your key looks like
dj_live_…and may be scoped to permits, parcels, contractors, or all three. - Send the key on every request as a bearer token.
- Make your first call:
curl "https://datajackpot.net/api/v1/permits?state=NY&permit_type=roofing&limit=5" \
-H "Authorization: Bearer dj_live_your_key_here"Python
import requests
BASE = "https://datajackpot.net/api/v1"
HEADERS = {"Authorization": "Bearer dj_live_your_key_here"}
r = requests.get(f"{BASE}/permits",
params={"state": "NY", "permit_type": "roofing", "limit": 5},
headers=HEADERS)
print(r.json()["data"])JavaScript
const res = await fetch(
"https://datajackpot.net/api/v1/permits?state=NY&permit_type=roofing&limit=5",
{ headers: { Authorization: "Bearer dj_live_your_key_here" } }
);
const { data } = await res.json();
console.log(data);Authentication
Send your key as a bearer token (or the X-API-Key header) on every request. Keys are secret. Keep them server-side. If a key is ever exposed, rotate it yourself from your account page.
Authorization: Bearer dj_live_your_key_hereEach key carries scopes that control which datasets it can read (permits, parcels, contractors). Calling an endpoint your key is not scoped for returns 403.
Permits
GET https://datajackpot.net/api/v1/permits: building permits, plus certificates of occupancy and new-construction parcels via record_class.
Parameters (all optional)
state: two-letter state, e.g.NY. Omit for nationwide.town: exact town name. Repeattowns[]for several. (See Browse coverage.)permit_type: category:roofing,solar,hvac,pool,new_construction, and more (see/permit-types).permit_type_exact: exact source type string (overrides the category).date_filed_from/date_filed_to:YYYY-MM-DD.date_issued_from/date_issued_to:YYYY-MM-DD.record_class:permit(default),certificate_of_occupancy,new_construction_parcel, orall.limit: 1 to 1000 per page (default 100).cursor: thenext_cursorfrom the previous page (see Paging).
Response shape
{
"data": [
{
"id": "…",
"town": "NYC - Brooklyn",
"state": "NY",
"permit_number": "B-2026-01234",
"permit_type": "roofing",
"permit_status": "issued",
"date_filed": "2026-03-31",
"date_issued": "2026-04-05",
"property_address": "75 Poplar St",
"owner_name": "…",
"contractor_name": "…",
"contractor_phone": "…",
"project_value": 18500,
"work_description": "Re-roof, asphalt shingle",
"record_class": "permit"
}
],
"returned": 100,
"next_cursor": "…" // null when there are no more pages
}Single record: GET https://datajackpot.net/api/v1/permits/{id}.
Parcels
GET https://datajackpot.net/api/v1/parcels: 80.5M assessor records: owner, year built, building size, land use, assessed value, and last sale. Requires the parcels scope.
curl "https://datajackpot.net/api/v1/parcels?state=OH&county_name=Franklin&year_built_from=2020&limit=100" \
-H "Authorization: Bearer dj_live_your_key_here"state,county_name,situs_city,use_codeyear_built_from/year_built_tosale_date_from/sale_date_to(YYYY-MM-DD)assessed_min/assessed_maxlimit,cursor
Property feed
A property-level view that combines everything above: the parcel record, its permit history, its violation and enforcement history (51.9M+ property events: housing violations, code enforcement, inspections, hearings), recorded transactions (deeds, mortgages, satisfactions, liens; NYC ACRIS today), assessor tax-roll history (assessed land/building/total by year), government meeting/agenda decisions that reference the parcel (variances, rezonings, site plans), a unified timeline, assessor owner info, and a risk score where available. Two endpoints: a detail lookup and a change stream for keeping a local copy fresh. Requires the properties scope. Available on API Growth.
Property detail
GET https://datajackpot.net/api/v1/properties/{parcelId}: one call, the full dossier for a parcel. parcelId is the numeric id from /parcels.
curl "https://datajackpot.net/api/v1/properties/84210993?events_limit=100&permits_limit=50" \
-H "Authorization: Bearer dj_live_your_key_here"events_limit: 1 to 200 latest events (default 100).permits_limit: 1 to 200 latest permits (default 50).transactions_limit: 1 to 200 latest recorded transactions (default 100).
Response shape (parcel id is a synthetic example)
{
"parcel": {
"id": 84210993,
"state": "NY",
"county_name": "Bronx",
"county_fips": "36005",
"apn": "2031440004",
"situs_address": "2086 Valentine Ave",
"situs_city": "Bronx",
"situs_zip": "10457",
"address_normalized": "2086 VALENTINE AVE",
"owner_name": "…",
"owner_mail_address": "…",
"year_built": 1927,
"building_sqft": 24800,
"lot_acres": 0.11,
"use_code": "C1",
"use_desc": "Walk-up apartments",
"assessed_value": 1250000,
"sale_date": "2019-08-14",
"sale_price": 3400000,
"latitude": 40.8541,
"longitude": -73.8996
},
"owner": { "name": "…", "mail_address": "…", "source": "assessor" },
"permits": {
"match_method": "address_normalized_exact",
"returned": 3,
"truncated": false,
"records": [ /* same shape as /permits rows */ ]
},
"events": {
"total": 187,
"returned": 100,
"open_in_returned": 12,
"by_record_class_in_returned": { "violation": 88, "code_enforcement": 12 },
"truncated": true,
"records": [
{
"id": 41827364,
"record_class": "violation",
"event_type": "heat-hot-water",
"event_class": "C",
"status": "open",
"event_date": "2026-01-22",
"disposition_date": null,
"description": "…",
"property_address": "2086 Valentine Ave",
"city": "Bronx",
"state": "NY",
"zip": "10457",
"parcel_apn": "2031440004",
"parcel_id": 84210993,
"parcel_match_method": "direct_key",
"parcel_match_confidence": 1,
"source_slug": "nyc-hpd-violations",
"source_record_url": "…"
}
]
},
"transactions": {
"match_method": "apn_exact",
"returned": 4,
"truncated": false,
"records": [
{
"doc_id": "2019081400412001",
"doc_class": "deed",
"doc_type": "DEED",
"amount": 3400000,
"recorded_date": "2019-08-14",
"executed_date": "2019-08-01",
"source_record_url": "…"
}
]
},
"assessments": {
"match_method": "apn_exact",
"returned": 5,
"truncated": false,
"records": [
{
"tax_year": 2026,
"assessed_land": 210000,
"assessed_building": 1040000,
"assessed_total": 1250000,
"property_class": "C1",
"source_slug": "nyc-dof-tax-roll",
"parcel_match_method": "direct_key",
"parcel_match_confidence": 1
}
]
},
"govDecisions": {
"match_method": "apn_contains",
"returned": 1,
"truncated": false,
"records": [
{
"platform": "legistar",
"client_slug": "nyc",
"external_id": "M-2026-04471",
"matter_type": "variance",
"title": "Application for a use variance at 2086 Valentine Ave",
"summary": "…",
"applicant": "…",
"addresses": ["2086 Valentine Ave"],
"parcel_apns": ["2031440004"],
"outcome": "approved",
"decided_date": "2025-11-04",
"city": "Bronx",
"source_record_url": "…"
}
]
},
"timeline": {
"returned": 108,
"truncated": false,
"entries": [
{
"date": "2026-01-22",
"kind": "event",
"record_class": "violation",
"summary": "heat-hot-water (open)",
"source_record_url": "…",
"match": { "method": "direct_key", "confidence": 1 }
},
{
"date": "2025-11-04",
"kind": "gov_decision",
"matter_type": "variance",
"summary": "Application for a use variance at 2086 Valentine Ave (approved)",
"source_record_url": "…",
"match": { "method": "apn_contains" }
},
{
"date": "2019-08-14",
"kind": "transaction",
"doc_class": "deed",
"summary": "DEED for $3,400,000",
"source_record_url": "…",
"match": { "method": "apn_exact" }
},
{
"date": "2018-03-02",
"kind": "permit",
"record_class": "permit",
"summary": "Plumbing: replace boiler",
"match": { "method": "address_normalized_exact" }
}
]
},
"risk": {
"bbl": "2031440004",
"label": "Elevated",
"reasons": ["…"],
"violations": { "total": 132, "open": 11, "open_class_c": 2 },
"complaints": { "total": 240, "open_now": 3, "last_12mo": 41 },
"hearings": { "oath_cases": 6, "penalties_imposed": 4800, "balance_due": 1200 },
"generated_at": "2026-07-08T09:12:00Z"
}
}eventsare linked to the parcel by direct source key (BBL/APN), never fuzzy matching;totalis exact andtruncatedtells you when more history exists than the page shows.permits.match_methodisaddress_normalized_exact(state + normalized address) ornonewhen the parcel has no normalized address.transactionsare ownership and mortgage recordings (deed, mortgage, satisfaction, lien, lis-pendens), matched by state plus recorder parcel number (BBL in NYC), newest first.match_methodisapn_exact, ornonewhen the parcel has no APN. Coverage is New York City (ACRIS) today. Party names are not included yet.assessmentsis assessor tax-roll history (land, building, and total assessed value bytax_year), matched by state plus recorder parcel number, newest year first, capped at 50 rows.match_methodisapn_exact, ornonewhen the parcel has no APN.govDecisionsis government meeting/agenda items (zoning variances, rezonings, site plans, appeals) that reference this parcel's APN, matched by state plus anparcel_apnsarray-contains check, newest decision first, capped at 50 rows.outcomeis one ofapproved,denied,tabled,continued,withdrawn, orunknown. Matching by address (rather than APN) is not supported yet.timelinemerges the permits, events, transactions, and gov decisions already in the response into one date-descending list, capped at 200 entries with atruncatedflag. Each entry carries akind(permit,event,transaction, orgov_decision), a shortsummary, and the match method that linked the record to the parcel. It is built from the returned blocks, so the*_limitparams bound what it can contain (assessments and gov decisions are always included up to their 50-row cap).riskis a deterministic, rule-based score over live public-record counts. Currently New York City parcels only;nullelsewhere, with arisk_notesaying why.
Delta: what changed since a date
GET https://datajackpot.net/api/v1/properties/delta: properties in a state with new activity since a date, grouped by property, oldest first. Built for daily sync jobs: pull once a day, checkpoint the cursor, and your copy stays fresh without re-downloading anything.
curl "https://datajackpot.net/api/v1/properties/delta?since=2026-07-01&state=NY&county_fips=36005&limit=500" \
-H "Authorization: Bearer dj_live_your_key_here"since(required):YYYY-MM-DD.state(required): two-letter state.county_fips: optional 5-digit FIPS (events stream only).stream:events(default) for violations / enforcement / inspections, orpermitsfor new permits.limit: 1 to 1000 records per page (default 500).cursor: thenext_cursorfrom the previous page.
Response shape (events stream; parcel id is a synthetic example)
{
"stream": "events",
"since": "2026-07-01",
"state": "NY",
"county_fips": "36005",
"properties": [
{
"parcel_id": 84210993,
"parcel_apn": "2031440004",
"state": "NY",
"county_fips": "36005",
"city": "Bronx",
"property_address": "2086 Valentine Ave",
"new_events": [
{
"id": 41911207,
"record_class": "violation",
"event_type": "lead-paint",
"event_class": "C",
"status": "open",
"event_date": "2026-07-02",
"disposition_date": null,
"description": "…",
"source_slug": "nyc-hpd-violations",
"source_record_url": "…"
}
]
}
],
"properties_returned": 312,
"events_returned": 500,
"next_cursor": "…" // null when you have everything
}- Records are keyed by their own date (
event_date/date_filed). Sources occasionally backfill history, so overlap yoursincewith the previous pull by a few days and dedupe on recordid. - A property with activity on several dates can appear on more than one page; dedupe on
parcel_iddownstream. - The
permitsstream groups by normalized address and returnsparcel_id: nullwith anew_permitsarray: permit-to-parcel linkage ships in a later release. Billing counts the records returned (events_returned/permits_returned), not the property groupings.
Contractors
GET https://datajackpot.net/api/v1/contractors: 3.3M licensed contractors: business name, license, classification, contact, bond and insurance. Requires the contractors scope.
curl "https://datajackpot.net/api/v1/contractors?state=FL&classification=gc&license_status=Active&limit=100" \
-H "Authorization: Bearer dj_live_your_key_here"state,city,countylicense_status,license_type,classificationbusiness_name: partial, case-insensitive matchlimit,cursor
Browse coverage
Town names are source-specific (e.g. NYC - Bronx). Look them up rather than guessing, so a typo does not return an empty result.
GET https://datajackpot.net/api/v1/states -> states we cover, with town + record counts
GET https://datajackpot.net/api/v1/towns?state=NY -> exact town names in a state, with counts
GET https://datajackpot.net/api/v1/permit-types -> permit_type categories + record typesA permit search that matches no town returns a hint field pointing you to /towns.
Paging through results
Results come in pages of up to 1000. Each response returns a next_cursor. Pass it back to get the next page, and stop when it is null. Pagination is stable and complete, even across tens of millions of rows.
import requests
BASE = "https://datajackpot.net/api/v1"
H = {"Authorization": "Bearer dj_live_your_key_here"}
cursor, all_rows = None, []
while True:
r = requests.get(f"{BASE}/permits",
params={"state": "NJ", "limit": 1000, "cursor": cursor},
headers=H).json()
all_rows += r["data"]
cursor = r["next_cursor"]
if not cursor:
break
print(len(all_rows), "permits")Webhooks & alerts
Subscribe a saved search and get new matching permits pushed to you within the daily refresh cycle, instead of polling the whole dataset. Save a filter once; every day, records that entered the database since your last delivery and match the filter are batched up and either POSTed to your endpoint (webhook mode) or held for you to pull from /notifications (pull mode). Alerts are forward-looking: you only receive records added after the subscription is created. Webhooks and /notifications require an API Growth plan (or a custom key with the webhooks scope).
Create a subscription
POST https://datajackpot.net/api/v1/webhooks. Include url for webhook delivery, or omit it for a pull-only subscription. Up to 10 subscriptions per key; requires the permits scope.
curl -X POST "https://datajackpot.net/api/v1/webhooks" \
-H "Authorization: Bearer dj_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/permits",
"description": "NY roofing daily",
"filter": {
"state": "NY",
"permit_type": "roofing",
"record_class": "permit"
}
}'Filter keys (all optional): state, county (requires state), town, towns (array), permit_type (category slug, see /permit-types), permit_type_exact, and record_class. An empty filter matches every new record nationwide.
The response includes a secret (whsec_…). Store it server-side: every webhook POST is signed with it. Manage subscriptions with GET https://datajackpot.net/api/v1/webhooks (list) and DELETE https://datajackpot.net/api/v1/webhooks/{id}.
What we send
One JSON POST per batch of up to 200 records (large days arrive as several batches). Respond with any 2xx within 10 seconds. Failures are retried with exponential backoff, up to 8 attempts.
POST https://example.com/hooks/permits
Content-Type: application/json
X-Jackpot-Signature: sha256=6b1f0c...
X-Jackpot-Delivery-Id: 6f0d...
X-Jackpot-Subscription-Id: 2a9c...
{
"subscription_id": "2a9c...",
"filter": { "state": "NY", "permit_type": "roofing" },
"generated_at": "2026-07-08T12:00:41Z",
"record_count": 37,
"records": [ { ...same fields as /permits... } ]
}Verify the signature by computing HMAC-SHA256 of the raw request body with your subscription secret and comparing it to X-Jackpot-Signature:
import hashlib, hmac
def verify(secret: str, body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)Pull instead of push
No public endpoint? Create the subscription without a url and poll GET https://datajackpot.net/api/v1/notifications. It returns the same batches, oldest first, and also serves as a replay log for webhook subscriptions.
GET https://datajackpot.net/api/v1/notifications?since=2026-07-07T00:00:00Zsince: ISO timestamp; only batches generated after it.subscription_id: restrict to one subscription.status:pending,delivered,failed,dead, orready(pull-only batches).limit(1-100 batches) andcursor(see Paging).
Typical loop: store the highest created_at you have seen and pass it back as since on the next poll. Records delivered through webhooks and notifications count toward usage the same way /permits records do.
Common recipes
New roofing permits in NY this month
GET https://datajackpot.net/api/v1/permits?state=NY&permit_type=roofing&date_filed_from=2026-06-01Certificates of occupancy in a state
GET https://datajackpot.net/api/v1/permits?state=NY&record_class=certificate_of_occupancyHomes built since 2020 in a county
GET https://datajackpot.net/api/v1/parcels?state=OH&county_name=Franklin&year_built_from=2020Active general contractors in Florida
GET https://datajackpot.net/api/v1/contractors?state=FL&classification=gc&license_status=ActiveRecent high-value sales
GET https://datajackpot.net/api/v1/parcels?state=NC&sale_date_from=2026-01-01&assessed_min=500000Daily sync: properties with new violations this week
GET https://datajackpot.net/api/v1/properties/delta?since=2026-07-01&state=NY&stream=eventsLimits & billing
- Up to 1000 records per page; page with
cursorfor more. - API Starter, $179/month: 25,000 records per month, 120 requests/minute.
- API Growth, $449/month: 150,000 records per month, 300 requests/minute, plus webhooks and the property feed.
- 1 credit = 1 record returned. Quotas reset each calendar month.
- Plans are a hard cap: once a month’s quota is exhausted, requests return
402with an upgrade prompt. - Check your usage anytime:
GET https://datajackpot.net/api/v1/usage. - Need more volume or bulk file delivery? Email henry@datajackpot.net.
Errors
400: missing or invalid parameter.401: missing or invalid key.402: usage limit reached (capped keys only).403: key disabled, or not scoped for that dataset.429: rate limit exceeded; slow down and retry.
Questions? henry@datajackpot.net · datajackpot.net