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.
X-API-Key: your_api_key_hereKeep 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:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per day (1000) |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | ISO 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": {
"code": "ERROR_CODE",
"message": "Human readable description."
}
}| HTTP Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid request parameters. |
| 401 | UNAUTHORIZED | Missing or invalid API key. |
| 403 | FORBIDDEN | API key valid but insufficient tier. |
| 404 | NOT_FOUND | Resource does not exist. |
| 429 | RATE_LIMIT_EXCEEDED | Daily rate limit exceeded. |
| 500 | INTERNAL_ERROR | Unexpected server error. |
/api/v1/permits
Search and filter building permits with pagination.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
city | string | optional | Comma-separated city IDs (e.g. austin_tx,dallas_tx) |
zip | string | optional | ZIP code filter |
address | string | optional | Case-insensitive address substring match |
type | string | optional | Comma-separated permit types: new_construction, renovation, demolition, electrical, plumbing, mechanical, roofing, other |
status | string | optional | Comma-separated statuses: filed, approved, issued, final, expired |
date_from | string | optional | Start date (YYYY-MM-DD), inclusive |
date_to | string | optional | End date (YYYY-MM-DD), inclusive |
value_min | number | optional | Minimum estimated value |
value_max | number | optional | Maximum estimated value |
contractor | string | optional | Case-insensitive contractor name match |
keyword | string | optional | Case-insensitive keyword search on project description |
page | integer | optional | Page number (default: 1) |
per_page | integer | optional | Results per page, 1-100 (default: 25) |
sort_by | string | optional | Sort field: filing_date, estimated_value, city, permit_type, etc. |
sort_order | string | optional | asc or desc (default: desc) |
Response
{
"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 -H "X-API-Key: YOUR_KEY" \
"https://permitpulse.com/api/v1/permits?city=austin_tx&type=new_construction&per_page=10"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"])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`);/api/v1/permits/:id
Retrieve a single permit by its unique ID.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | required | The permit UUID |
Response
{
"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,
...
}
}{
"error": {
"code": "NOT_FOUND",
"message": "Permit not found."
}
}Examples
curl -H "X-API-Key: YOUR_KEY" \
"https://permitpulse.com/api/v1/permits/550e8400-e29b-41d4-a716-446655440000"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"])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);/api/v1/permits/recent
Fetch the most recently filed permits, sorted by filing date descending.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
city | string | optional | Comma-separated city IDs to filter by |
zip | string | optional | ZIP code filter |
limit | integer | optional | Number of results, 1-100 (default: 25) |
Response
{
"data": [ ... ],
"meta": {
"total": 4281
}
}Examples
curl -H "X-API-Key: YOUR_KEY" \
"https://permitpulse.com/api/v1/permits/recent?city=dallas_tx&limit=10"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"])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));/api/v1/cities
List all cities currently available for permit data. Only enabled cities are returned.
Response
{
"data": [
{
"city_id": "austin_tx",
"display_name": "Austin, TX",
"state": "TX"
},
{
"city_id": "dallas_tx",
"display_name": "Dallas, TX",
"state": "TX"
}
]
}Examples
curl -H "X-API-Key: YOUR_KEY" \
"https://permitpulse.com/api/v1/cities"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"])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));/api/v1/account/usage
Check your current API usage and rate-limit status.
Response
{
"data": {
"requests_today": 42,
"requests_limit": 1000,
"resets_at": "2026-04-07T00:00:00.000Z",
"tier": "pro"
}
}Examples
curl -H "X-API-Key: YOUR_KEY" \
"https://permitpulse.com/api/v1/account/usage"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']}")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.