API documentation.

Authentication, request and response shapes, error codes, rate limits, and quotas. Real numbers, real HTTP codes, copy-pasteable curl.

Contents
  1. Authentication
  2. Endpoints
  3. Request shape
  4. Response shape
  5. Errors
  6. Rate limits
  7. Quotas

Authentication

Every request must include a Bearer token in theAuthorizationheader. Tokens always start withpp_live_followed by 32 lowercase characters.

Generate a key on /account/api-keys. The full key shows once on creation; only the SHA-256 hash is stored server-side. There is no way to recover a lost key — generate a new one.

Sandbox keys and paid keys are functionally identical. They differ only in monthly quota and per-minute rate limit. Code that works against a sandbox key works unchanged against a Developer or Business key.

Endpoints

  • POST/v1/researchRun permit research for one address + job. One call = one search.
  • GET/v1/usageCurrent quota and rate limit state.Planned

Request shape

POST a JSON body to /v1/research. All fields are top-level — no nested envelope.

Example

curl https://api.permitpull.ai/v1/research \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"address": "123 Main St, Springfield, IL 62701",
"job_type": "roof replacement",
"occupancy_type": "residential",
"user_type": "contractor"
}'

Fields

FieldTypeDescription
addressRequiredstringFull street address. Google Places-style format works best — e.g. "123 Main St, Springfield, IL 62701" (placeholder shown; supply a real US address).
job_typeRequiredstringFree-text description of the job — "roof replacement", "kitchen remodel", "ADU construction". Validated server-side; ambiguous values may return a 400.
occupancy_typeRequiredstringOne of: "residential", "commercial", "mixed_use", "industrial".
user_typestringOne of: "contractor", "homeowner". Drives report tone and section emphasis. Defaults to "contractor".

Response shape

Successful responses return HTTP 200 with the full report body. The metadata block at the end of every response carries the caller’s current quota and rate limit state — track these to avoid 429s.

Full response

{
"id": "00000000-0000-0000-0000-000000000000",
"jurisdiction": {
"name": "Sangamon County, IL",
"code_edition": "2021 IRC",
"phone": "+1-555-867-5309",
"website": "https://www.example-county.gov/codes/"
},
"permit_required": true,
"permits": [
{
"type": "Building Permit",
"issuer": "Sangamon County Building Safety",
"fee": "$108.00",
"fee_source": "ResidentialPermitFees.pdf"
}
],
"fees": {
"total_estimated": "$108.00",
"breakdown": [
{ "label": "Plan review", "amount": "$33.00" },
{ "label": "Permit", "amount": "$75.00" }
]
},
"forms": [
{
"name": "Residential Building Permit Application",
"url": "https://www.example-county.gov/forms/RBPA.pdf",
"format": "PDF"
}
],
"timeline": "3-5 business days for issuance",
"inspections": [
{ "phase": "Pre-construction", "trigger": "Materials on site" },
{ "phase": "Final", "trigger": "Project complete" }
],
"code_flags": [
{
"label": "Wind zone",
"note": "Wind zone II — fastener schedule may differ"
}
],
"metadata": {
"tier": "developer",
"searches_remaining": 287,
"rate_limit_remaining": 9,
"rate_limit_reset_seconds": 42
}
}

Top-level fields

FieldTypeDescription
idRequireduuidUnique identifier for this research result. Persistent — use it to reference the report later.
jurisdictionRequiredobjectConfirmed jurisdiction for the address. Includes name, building code edition, phone, website.
permit_requiredRequiredbooleanWhether the job requires a permit at all. False is a valid result for some minor work.
permitsRequiredarray<object>List of required permits with fees and issuing authority.
feesRequiredobjectTotal estimated fees plus a per-line breakdown.
formsRequiredarray<object>Required application forms with direct URLs to government PDFs.
timelineRequiredstringPublished or estimated processing time.
inspectionsRequiredarray<object>Required inspection phases and their triggers.
code_flagsRequiredarray<object>Job-specific or jurisdiction-specific code considerations (wind zone, hazard zone, hardscape limits, etc.).
metadataRequiredobjectQuota and rate limit state for this caller. See "Rate limits" + "Quotas".

Errors

All errors return a JSON body with an error object containing a machine-readable code, a human-readable message, and (where applicable) hint fields like retry_after_seconds.

Error response

{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit reached. Retry in 42 seconds.",
"retry_after_seconds": 42
}
}

Status codes

StatusCodeDescription
400invalid_requestMissing or malformed required field. Response body specifies which.
401invalid_keyMissing, malformed, or non-existent API key. Check the Authorization header is `Bearer pp_live_…`.
402payment_requiredSubscription lapsed or trial exhausted. Response body suggests an upgrade path.
403key_revokedAPI key has been revoked. Generate a new key.
422unprocessable_addressAddress could not be resolved to a U.S. jurisdiction. Confirm street + city + state + ZIP.
429rate_limit_exceededToo many requests. Honor the `Retry-After` header and `retry_after_seconds` field.
429quota_exceededMonthly cap reached. Upgrade tier or buy a credit pack.
500internal_errorUnexpected server-side failure. Safe to retry once after a short backoff.
503upstream_unavailableGovernment source temporarily unreachable. Retry with exponential backoff.

Rate limits

Per-minute limits scale with tier:

  • Sandbox: 10 req/min (shared with Developer).
  • Developer: 10 req/min.
  • Business: 60 req/min.

Exceeding the limit returns HTTP 429 with a Retry-After response header (seconds until the next allowed request) and a retry_after_seconds field in the error body. Honor either signal — they’re identical.

Quotas

Monthly limits scale with tier:

  • Sandbox: 10 lifetime searches (no monthly reset — once you’ve used 10, the key returns 402 until you upgrade).
  • Developer: 300 searches per calendar month.
  • Business: 1,000 searches per calendar month.

Hard stop at the cap. There is no overage. Reaching the limit returns HTTP 429 with code quota_exceeded. Upgrade tier, buy a credit pack, or wait for the next month.

Track remaining quota via the metadata.searches_remaining field on every successful response. The GET /v1/usage endpoint will offer the same signal without consuming a search quota when shipped Planned.

Still have questions?

We’ll help you ship it.

Email is the fastest path. Most replies arrive within one business day.