API Reference

The PermitPulse REST API lets you programmatically search and retrieve building permit data. All endpoints return JSON and require a Pro-tier API key.

Base URL: https://permitpulse.com/api/v1

Authentication

All API requests must include your API key in the X-API-Key header. API keys are available to Pro plan subscribers and can be generated from your account settings.

Example header
X-API-Key: your_api_key_here

Keep your API key secret. If compromised, regenerate it from your account page. The previous key is immediately invalidated.

Rate Limiting

API requests are limited to 1,000 requests per day per account. The daily window resets at midnight UTC.

Every response includes rate-limit headers so you can track your usage:

HeaderDescription
X-RateLimit-LimitMaximum requests per day (1000)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetISO 8601 timestamp when the window resets

When the limit is exceeded, you will receive a 429 response with a Retry-After header (seconds until reset).

Error Format

All errors follow a consistent JSON structure:

Error response
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human readable description."
  }
}
HTTP StatusCodeMeaning
400VALIDATION_ERRORInvalid request parameters.
401UNAUTHORIZEDMissing or invalid API key.
403FORBIDDENAPI key valid but insufficient tier.
404NOT_FOUNDResource does not exist.
429RATE_LIMIT_EXCEEDEDDaily rate limit exceeded.
500INTERNAL_ERRORUnexpected server error.
GET

/api/v1/permits

Search and filter building permits with pagination.

Query Parameters

ParameterTypeRequiredDescription
citystringoptionalComma-separated city IDs (e.g. austin_tx,dallas_tx)
zipstringoptionalZIP code filter
addressstringoptionalCase-insensitive address substring match
typestringoptionalComma-separated permit types: new_construction, renovation, demolition, electrical, plumbing, mechanical, roofing, other
statusstringoptionalComma-separated statuses: filed, approved, issued, final, expired
date_fromstringoptionalStart date (YYYY-MM-DD), inclusive
date_tostringoptionalEnd date (YYYY-MM-DD), inclusive
value_minnumberoptionalMinimum estimated value
value_maxnumberoptionalMaximum estimated value
contractorstringoptionalCase-insensitive contractor name match
keywordstringoptionalCase-insensitive keyword search on project description
pageintegeroptionalPage number (default: 1)
per_pageintegeroptionalResults per page, 1-100 (default: 25)
sort_bystringoptionalSort field: filing_date, estimated_value, city, permit_type, etc.
sort_orderstringoptionalasc or desc (default: desc)

Response

200 OK
{
  "data": [
    {
      "id": "uuid",
      "permitNumber": "BP-2026-00142",
      "filingDate": "2026-04-03T00:00:00.000Z",
      "permitType": "new_construction",
      "streetAddress": "1247 Oak Valley Dr",
      "city": "Austin",
      "state": "TX",
      "zipCode": "78745",
      "latitude": 30.2132,
      "longitude": -97.7698,
      "projectDescription": "Single family residence...",
      "estimatedValue": 485000,
      "contractorName": "Hill Country Builders",
      "permitStatus": "filed",
      "sourceCityId": "austin_tx",
      "sourceUrl": "https://..."
    }
  ],
  "meta": {
    "total": 1523,
    "page": 1,
    "per_page": 25,
    "total_pages": 61
  }
}

Examples

cURL
curl -H "X-API-Key: YOUR_KEY" \
  "https://permitpulse.com/api/v1/permits?city=austin_tx&type=new_construction&per_page=10"
Python
import requests

resp = requests.get(
    "https://permitpulse.com/api/v1/permits",
    headers={"X-API-Key": "YOUR_KEY"},
    params={
        "city": "austin_tx",
        "type": "new_construction",
        "per_page": 10,
    },
)
data = resp.json()
for permit in data["data"]:
    print(permit["permitNumber"], permit["streetAddress"])
JavaScript (fetch)
const params = new URLSearchParams({
  city: "austin_tx",
  type: "new_construction",
  per_page: "10",
});

const res = await fetch(
  `https://permitpulse.com/api/v1/permits?${params}`,
  { headers: { "X-API-Key": "YOUR_KEY" } }
);

const { data, meta } = await res.json();
console.log(`Found ${meta.total} permits`);
GET

/api/v1/permits/:id

Retrieve a single permit by its unique ID.

Path Parameters

ParameterTypeRequiredDescription
idstringrequiredThe permit UUID

Response

200 OK
{
  "data": {
    "id": "uuid",
    "permitNumber": "BP-2026-00142",
    "filingDate": "2026-04-03T00:00:00.000Z",
    "permitType": "new_construction",
    "streetAddress": "1247 Oak Valley Dr",
    "city": "Austin",
    "state": "TX",
    "zipCode": "78745",
    "estimatedValue": 485000,
    ...
  }
}
404 Not Found
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Permit not found."
  }
}

Examples

cURL
curl -H "X-API-Key: YOUR_KEY" \
  "https://permitpulse.com/api/v1/permits/550e8400-e29b-41d4-a716-446655440000"
Python
import requests

permit_id = "550e8400-e29b-41d4-a716-446655440000"
resp = requests.get(
    f"https://permitpulse.com/api/v1/permits/{permit_id}",
    headers={"X-API-Key": "YOUR_KEY"},
)
permit = resp.json()["data"]
print(permit["permitNumber"])
JavaScript (fetch)
const id = "550e8400-e29b-41d4-a716-446655440000";
const res = await fetch(
  `https://permitpulse.com/api/v1/permits/${id}`,
  { headers: { "X-API-Key": "YOUR_KEY" } }
);
const { data } = await res.json();
console.log(data.permitNumber);
GET

/api/v1/permits/recent

Fetch the most recently filed permits, sorted by filing date descending.

Query Parameters

ParameterTypeRequiredDescription
citystringoptionalComma-separated city IDs to filter by
zipstringoptionalZIP code filter
limitintegeroptionalNumber of results, 1-100 (default: 25)

Response

200 OK
{
  "data": [ ... ],
  "meta": {
    "total": 4281
  }
}

Examples

cURL
curl -H "X-API-Key: YOUR_KEY" \
  "https://permitpulse.com/api/v1/permits/recent?city=dallas_tx&limit=10"
Python
import requests

resp = requests.get(
    "https://permitpulse.com/api/v1/permits/recent",
    headers={"X-API-Key": "YOUR_KEY"},
    params={"city": "dallas_tx", "limit": 10},
)
for permit in resp.json()["data"]:
    print(permit["filingDate"], permit["streetAddress"])
JavaScript (fetch)
const res = await fetch(
  "https://permitpulse.com/api/v1/permits/recent?city=dallas_tx&limit=10",
  { headers: { "X-API-Key": "YOUR_KEY" } }
);
const { data } = await res.json();
data.forEach(p => console.log(p.filingDate, p.streetAddress));
GET

/api/v1/cities

List all cities currently available for permit data. Only enabled cities are returned.

Response

200 OK
{
  "data": [
    {
      "city_id": "austin_tx",
      "display_name": "Austin, TX",
      "state": "TX"
    },
    {
      "city_id": "dallas_tx",
      "display_name": "Dallas, TX",
      "state": "TX"
    }
  ]
}

Examples

cURL
curl -H "X-API-Key: YOUR_KEY" \
  "https://permitpulse.com/api/v1/cities"
Python
import requests

resp = requests.get(
    "https://permitpulse.com/api/v1/cities",
    headers={"X-API-Key": "YOUR_KEY"},
)
for city in resp.json()["data"]:
    print(city["city_id"], city["display_name"])
JavaScript (fetch)
const res = await fetch(
  "https://permitpulse.com/api/v1/cities",
  { headers: { "X-API-Key": "YOUR_KEY" } }
);
const { data } = await res.json();
console.log(data.map(c => c.display_name));
GET

/api/v1/account/usage

Check your current API usage and rate-limit status.

Response

200 OK
{
  "data": {
    "requests_today": 42,
    "requests_limit": 1000,
    "resets_at": "2026-04-07T00:00:00.000Z",
    "tier": "pro"
  }
}

Examples

cURL
curl -H "X-API-Key: YOUR_KEY" \
  "https://permitpulse.com/api/v1/account/usage"
Python
import requests

resp = requests.get(
    "https://permitpulse.com/api/v1/account/usage",
    headers={"X-API-Key": "YOUR_KEY"},
)
usage = resp.json()["data"]
print(f"Used {usage['requests_today']} / {usage['requests_limit']}")
JavaScript (fetch)
const res = await fetch(
  "https://permitpulse.com/api/v1/account/usage",
  { headers: { "X-API-Key": "YOUR_KEY" } }
);
const { data } = await res.json();
console.log(`${data.requests_today} / ${data.requests_limit} requests used`);

Try It

Try It

Test the API directly from this page. Requires a Pro API key.