API Reference

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

  1. 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.
  2. Send the key on every request as a bearer token.
  3. 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_here

Each 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. Repeat towns[] 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, or all.
  • limit: 1 to 1000 per page (default 100).
  • cursor: the next_cursor from 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_code
  • year_built_from / year_built_to
  • sale_date_from / sale_date_to (YYYY-MM-DD)
  • assessed_min / assessed_max
  • limit, 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"
  }
}
  • events are linked to the parcel by direct source key (BBL/APN), never fuzzy matching; total is exact and truncated tells you when more history exists than the page shows.
  • permits.match_method is address_normalized_exact (state + normalized address) or none when the parcel has no normalized address.
  • transactions are ownership and mortgage recordings (deed, mortgage, satisfaction, lien, lis-pendens), matched by state plus recorder parcel number (BBL in NYC), newest first. match_method is apn_exact, or none when the parcel has no APN. Coverage is New York City (ACRIS) today. Party names are not included yet.
  • assessments is assessor tax-roll history (land, building, and total assessed value by tax_year), matched by state plus recorder parcel number, newest year first, capped at 50 rows. match_method is apn_exact, or none when 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 an parcel_apns array-contains check, newest decision first, capped at 50 rows. outcome is one of approved, denied, tabled, continued, withdrawn, or unknown. Matching by address (rather than APN) is not supported yet.
  • timeline merges the permits, events, transactions, and gov decisions already in the response into one date-descending list, capped at 200 entries with a truncated flag. Each entry carries a kind (permit, event, transaction, or gov_decision), a short summary, and the match method that linked the record to the parcel. It is built from the returned blocks, so the *_limit params bound what it can contain (assessments and gov decisions are always included up to their 50-row cap).
  • risk is a deterministic, rule-based score over live public-record counts. Currently New York City parcels only; null elsewhere, with a risk_note saying 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, or permits for new permits.
  • limit: 1 to 1000 records per page (default 500).
  • cursor: the next_cursor from 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 your since with the previous pull by a few days and dedupe on record id.
  • A property with activity on several dates can appear on more than one page; dedupe on parcel_id downstream.
  • The permits stream groups by normalized address and returns parcel_id: null with a new_permits array: 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, county
  • license_status, license_type, classification
  • business_name: partial, case-insensitive match
  • limit, 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 types

A 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:00Z
  • since: ISO timestamp; only batches generated after it.
  • subscription_id: restrict to one subscription.
  • status: pending, delivered, failed, dead, or ready (pull-only batches).
  • limit (1-100 batches) and cursor (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-01

Certificates of occupancy in a state

GET https://datajackpot.net/api/v1/permits?state=NY&record_class=certificate_of_occupancy

Homes built since 2020 in a county

GET https://datajackpot.net/api/v1/parcels?state=OH&county_name=Franklin&year_built_from=2020

Active general contractors in Florida

GET https://datajackpot.net/api/v1/contractors?state=FL&classification=gc&license_status=Active

Recent high-value sales

GET https://datajackpot.net/api/v1/parcels?state=NC&sale_date_from=2026-01-01&assessed_min=500000

Daily sync: properties with new violations this week

GET https://datajackpot.net/api/v1/properties/delta?since=2026-07-01&state=NY&stream=events

Limits & billing

  • Up to 1000 records per page; page with cursor for 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 402 with 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