Coming soon. The API is not open yet. Every request is refused with HTTP 503 and the problem type not-yet-open until launch, and sign-up opens then. The API Terms are still being reviewed.
Parcel facts with their sources
Look up a parcel by address, point or county and parcel number, then read its facts by section: basics, assessment, tax, sales, zoning and overlays. Every fact names its source, source URL, retrieved date and license class. Official government pages for the parcel come from GISFinder. This is built for one parcel or one question at a time, not for bulk export.
Rules
- No FCRA uses. This data may not be used for credit, insurance, employment, tenant screening, housing or any other eligibility decision.
- The county is the authority. Parcel maps are not surveys. The county is the authority for boundaries, ownership, values and taxes; check the county record, or a licensed surveyor for boundaries, before relying on any fact here.
- Overlays are screening. Flood, wetland and soil shares are screening from mapped layers, not an official determination. The FEMA flood layer covers most but not all of the country.
- No person search. There is no owner-name search. Without a key you never get owner names or mailing addresses. With a key they appear only where the county's sources and state policy allow it, and never in area search. Phones and emails are never part of any response.
- Removed means removed. When a parcel you hold answers 404, delete your copy; when it answers
superseded, use its successors.
- Only counties and records whose source is licensed
open or public_record are served. GET /v1/coverage lists them.
Quickstart
No key is needed to start. Find the parcel:
curl 'https://api.publicgis.com/v1/parcels/lookup?address=123+Main+St+Springfield+MN'
Take a parcel_uid from the candidates and read its facts, or just the sections you need:
curl 'https://api.publicgis.com/v1/parcels/pg_01J9ZX3T5C8E2D7Q4W6B1N0MRA?sections=basics,assessment'
curl 'https://api.publicgis.com/v1/parcels/pg_01J9ZX3T5C8E2D7Q4W6B1N0MRA/overlays'
curl 'https://api.publicgis.com/v1/parcels/pg_01J9ZX3T5C8E2D7Q4W6B1N0MRA/official-resources?topic=assessor'
Parcels in a small area, filtered by acreage or land use:
curl 'https://api.publicgis.com/v1/parcels?lat=46.1005&lon=-93.0995&radius_m=500&min_acres=5'
With a key, send it as a bearer token:
curl -H 'Authorization: Bearer pgk_...' 'https://api.publicgis.com/v1/parcels/lookup?lat=46.1005&lon=-93.0995'
Every answer is the envelope data, sources, as_of, request_id, notices. Errors are RFC 9457 application/problem+json. Coordinates are WGS84, longitude first.
Keys
Without a key you are limited per IP address to 60 requests a minute and 1000 a day. With a key: 300 a minute and 20000 a day. A key is shown once and stored only as a digest; keep it on your server, never in browser code.
Get a key
Sign-up is not open yet. It opens with the API.
Accept the API Terms and get your key
Limits, caching and CORS
- Every response carries
RateLimit-Policy (for example "minute";q=60;w=60, "day";q=1000;w=86400) and RateLimit (what remains in each window and the seconds until it resets). Over a limit you get 429 with Retry-After; wait that long.
- Reads carry
ETag and Cache-Control: private, max-age=60. Send If-None-Match to get 304. The ETag changes whenever a privacy change is published, so do not keep answers past their freshness without revalidating.
- Area search is capped at 2 square kilometres or a 1000 metre radius and 50 parcels a page. Pass
next_cursor back as cursor with the same query; the order is stable.
- Browsers on any origin may call the anonymous tier (
Access-Control-Allow-Origin: *, no credentials). Keyed requests are for servers.
Errors
The type of every problem is https://www.publicgis.com/problems/ followed by one of: invalid-request ◦ invalid-key ◦ not-found ◦ no-match ◦ license-not-cleared ◦ method-not-allowed ◦ rate-limited ◦ not-yet-open ◦ unavailable ◦ server-error.
Versions and deprecation
This is version 1 under /v1. New fields and endpoints may be added at any time; ignore what you do not know. An endpoint, parameter or field is removed no sooner than 12 months after it is announced in the changelog, and deprecated responses carry Deprecation and Sunset headers.
MCP server for AI agents
The same data is offered to AI agents at https://api.publicgis.com/mcp (Streamable HTTP, read-only tools find_parcel, get_parcel_report, check_parcel_overlays, find_parcels_in_area, get_official_resources, get_coverage). See llms.txt.
Reference
Generated from the OpenAPI 3.1 document, which is the source of truth.
GET /v1/parcels/lookup
Up to five parcel candidates for an address, a point, or a county and APN. Give exactly one of address; lat and lon; or county_geoid and apn. An address is matched only against the parcel's own site address, city, ZIP and county, never owner names, so this is not a person search.
| Parameter | In | Type | Notes |
address | query | string | A street address, for example 123 Main St Springfield MN |
lat | query | number | WGS84 latitude |
lon | query | number | WGS84 longitude |
county_geoid | query | string | Five-digit county GEOID, with or without the county: prefix |
apn | query | string | The county's parcel number as published |
200 Candidates, best first, with a confidence between 0 and 1304 The ETag in If-None-Match still matches; reuse your copy.400 RFC 9457 problem details401 RFC 9457 problem details404 No parcel this tier may see matches; coverage names the county when one is known405 RFC 9457 problem details429 Over a rate limit. Wait Retry-After seconds; retrying sooner is refused again.503 RFC 9457 problem details
GET /v1/parcels/{parcel_uid}
Facts for one parcel by section, each with its sources and as_of date. A retired parcel_uid answers with status superseded and the live successors. A parcel that is hidden, suppressed or protected answers 404; delete any copy you hold.
| Parameter | In | Type | Notes |
parcel_uid (required) | path | ParcelUid | |
sections | query | string | Comma-separated subset of basics, assessment, tax, sales, zoning, overlays. Default all. |
geometry | query | one of simplified, full, none | GeoJSON outline in basics, simplified to about a metre by default. |
200 The parcel, or where it went304 The ETag in If-None-Match still matches; reuse your copy.400 RFC 9457 problem details401 RFC 9457 problem details403 RFC 9457 problem details404 RFC 9457 problem details405 RFC 9457 problem details429 Over a rate limit. Wait Retry-After seconds; retrying sooner is refused again.503 RFC 9457 problem details
GET /v1/parcels/{parcel_uid}/overlays
Flood zones, wetlands and soils as shares of the parcel's mapped area. Screening from mapped layers, not an official flood determination or wetland delineation. FEMA's flood layer covers most but not all of the country; an empty flood list where no FEMA layer exists is not a finding of no flood risk.
| Parameter | In | Type | Notes |
parcel_uid (required) | path | ParcelUid | |
200 Overlay shares with their source dates, or where the parcel went304 The ETag in If-None-Match still matches; reuse your copy.401 RFC 9457 problem details403 RFC 9457 problem details404 RFC 9457 problem details405 RFC 9457 problem details429 Over a rate limit. Wait Retry-After seconds; retrying sooner is refused again.503 RFC 9457 problem details
GET /v1/parcels/{parcel_uid}/official-resources
Official government resources for the parcel's place, from GISFinder. Ranked official pages for one topic from GISFinder's directory (www.gisfinder.com), for the smallest jurisdiction containing the parcel and then its county and state, each with GISFinder's verification status. This is GISFinder's compilation, not PublicGIS data. Answers 503 unavailable when GISFinder cannot be reached.
| Parameter | In | Type | Notes |
parcel_uid (required) | path | ParcelUid | |
topic (required) | query | Topic | |
200 Resources, best first304 The ETag in If-None-Match still matches; reuse your copy.400 RFC 9457 problem details401 RFC 9457 problem details403 RFC 9457 problem details404 RFC 9457 problem details405 RFC 9457 problem details429 Over a rate limit. Wait Retry-After seconds; retrying sooner is refused again.503 RFC 9457 problem details
GET /v1/parcels
Parcels in a small box or circle, filtered by acreage and land use. Give bbox, or lat, lon and radius_m. The box may cover at most 2 square kilometres and the radius
at most 1000 metres. At most 50 parcels per page, in a stable order; pass next_cursor as cursor
for the next page with the same query. Never returns owner names or mailing addresses, at any tier.
| Parameter | In | Type | Notes |
bbox | query | string | min_lon,min_lat,max_lon,max_lat in WGS84 |
lat | query | number | |
lon | query | number | |
radius_m | query | number | |
min_acres | query | number | |
max_acres | query | number | |
land_use | query | string | Case-insensitive text found in the county's land use class |
limit | query | integer | |
cursor | query | string | |
200 The count of matching parcels and one page of summaries304 The ETag in If-None-Match still matches; reuse your copy.400 RFC 9457 problem details401 RFC 9457 problem details405 RFC 9457 problem details429 Over a rate limit. Wait Retry-After seconds; retrying sooner is refused again.503 RFC 9457 problem details
GET /v1/coverage
Which counties the API serves and how fresh they are. Without jurisdiction_id, every county served through the API (license cleared), optionally for one state. With jurisdiction_id, whether PublicGIS shows that jurisdiction on the web and whether it is cleared for the API.
| Parameter | In | Type | Notes |
state | query | string | Two-letter postal code |
jurisdiction_id | query | string | |
200 Coverage304 The ETag in If-None-Match still matches; reuse your copy.400 RFC 9457 problem details401 RFC 9457 problem details404 RFC 9457 problem details405 RFC 9457 problem details429 Over a rate limit. Wait Retry-After seconds; retrying sooner is refused again.503 RFC 9457 problem details
Schemas
LicenseClass one of open, public_record, restricted_no_redistribution, licensed_third_party, unknown
ParcelUid string
Opaque, stable parcel id (pg_ and a ULID). It survives re-imports; a split or merged parcel's old id answers superseded.
JurisdictionId string
Topic one of parcel_map, county_gis, assessor, property_tax_payment, tax_collector, recorder_deeds, plats_surveys, zoning, building_permits, septic_well, wetlands, floodplain, soils, elevation, roads_right_of_way, public_land, property_records_general
GISFinder's topic vocabulary (platform contract 4.3)
Instant string or null
ISO 8601, a UTC date-time ending in Z or a calendar date
Value string or number or boolean or null or array or object
A published value, as the source gives it
Text string or null
Number number or null
Geometry object
GeoJSON, WGS84, longitude first
type | one of Point, Polygon, MultiPolygon | |
coordinates | array | |
Source object
id | string | |
name | string | |
url | string or null | |
publisher | string | |
official | boolean | |
retrieved_at | Instant | |
license_class | LicenseClass | |
as_of | Instant | |
Envelope object
data | any | |
sources | array of Source | |
as_of | Instant | |
request_id | string | |
notices | array of string | Plain-language caveats, always including that parcel maps are not surveys and the FCRA ban |
Candidate object
parcel_uid | ParcelUid | |
publicgis_url | string | |
jurisdiction_id | string | |
county_name | string | |
state | string or null | |
apn | string | |
confidence | number | |
matched_by | one of address, point, apn | |
situs_address | Text | |
situs_city | Text | |
situs_zip | Text | |
LookupEnvelope object
SectionStatus one of ok, empty, not_cleared, unavailable
ok; empty (nothing published); not_cleared (a source exists but its license does not allow the API); unavailable (try again later)
Section object
status | SectionStatus | |
as_of | Instant | |
sources | array of Source | |
facts | object | |
BasicsFacts object
owner_name, taxpayer_name and both mailing addresses appear only for a key whose holder accepted the current API Terms, and only where the county's parcel and owner sources are cleared, allow reuse of person fields and the state policy allows them. Never without a key.
apn | Value | |
owner_name | Value | |
taxpayer_name | Value | |
owner_mailing_address | Value | |
taxpayer_mailing_address | Value | |
situs_house_number | Value | |
situs_address | Value | |
situs_city | Value | |
situs_zip | Value | |
municipality | Value | |
plss_township | Value | |
plss_township_direction | Value | |
plss_range | Value | |
plss_range_direction | Value | |
plss_section | Value | |
acres_reported | Value | |
deeded_acres | Value | |
legal_description | Value | |
plat | Value | |
lake_name | Value | |
land_use_class | Value | |
land_use_class_2 | Value | |
land_use_class_3 | Value | |
school_district | Value | |
tax_district | Value | |
year_built | Value | |
bedrooms | Value | |
bathrooms | Value | |
living_sqft | Value | |
building_sqft | Value | |
acres_mapped | Value | |
geometry | Geometry | |
center | Geometry | |
AssessmentRecord object
land_value | Value | |
note | Value | |
improvement_value | Value | |
assessment_year | Value | |
market_value | Value | |
assessed_value | Value | |
class | Value | |
card | Value | |
taxable_value | Value | |
roll_stage | Value | |
school_assessed_value | Value | |
school_taxable_value | Value | |
year_built | Value | |
living_sqft | Value | |
residential_units | Value | |
buildings | Value | |
forest_law_value | Value | |
tax_year | Value | |
use_code | Value | |
auxiliary_class | Value | |
deeded_acres | Value | |
tillable_acres | Value | |
pay_year | Value | |
tax_capacity | Value | |
site_value | Value | |
dwelling_value | Value | |
equalized_value | Value | |
classes | Value | |
source_id | Value | |
last_published | Value | |
TaxRecord object
Balances, penalties, payment and delinquency status are never served by the API.
tax_year | Value | |
note | Value | |
derived_fields | Value | |
net_tax | Value | |
gross_tax | Value | |
tax_total | Value | |
first_half_tax | Value | |
second_half_tax | Value | |
installment_1_amount | Value | |
installment_2_amount | Value | |
special_assessments | Value | |
class | Value | |
prior_year_total | Value | |
source_id | Value | |
last_published | Value | |
SaleRecord object
Flags that describe the buyer or seller (primary residence, related parties, gift or inheritance, distressed) are never served by the API. A multi-parcel sale price is the price of the whole transaction.
sale_date | Value | |
sale_month | Value | |
sale_price | Value | |
adjusted_price | Value | |
sale_price_kind | Value | |
value_at_sale | Value | |
ratio | Value | |
qualified | Value | |
qualification_code | Value | |
reject_reason | Value | |
instrument | Value | |
document | Value | |
book | Value | |
page | Value | |
recorded_date | Value | |
deed_type | Value | |
sale_type | Value | |
status | Value | |
class_code | Value | |
property_class | Value | |
property_type | Value | |
multi_parcel | Value | |
multi_parcel_code | Value | |
improved | Value | |
jurisdiction | Value | |
deeded_acres | Value | |
year_built | Value | |
study_year | Value | |
note | Value | |
ecrv | Value | |
ecrv_id | Value | |
certificate_stage | Value | |
net_amount | Value | |
personal_property | Value | |
partial_interest | Value | |
government_sale | Value | |
new_construction | Value | |
split_or_combined | Value | |
included | Value | |
planned_use | Value | |
prior_use | Value | |
source_id | Value | |
last_published | Value | |
rejected | Value | |
sale_year | Value | |
tillable_acres | Value | |
transaction_parcels | Value | |
AssessmentFacts object
assessment_year | Value | |
land_value | Value | |
building_value | Value | |
total_value | Value | |
market_value | Value | |
value_kind | Value | |
assessed_value | Value | |
assessed_kind | Value | |
assessed_year | Value | |
records | array of AssessmentRecord | |
TaxFacts object
records | array of TaxRecord | |
SalesFacts object
last_sale_price | Value | |
last_sale_date | Value | |
sale_price_kind | Value | |
records | array of SaleRecord | |
ZoningFacts object
zoning | array of object | |
land_use | array of object | |
OverlayFacts object
Shares of the parcel's mapped area, in percent. Screening only.
flood | array of object | |
wetlands | array of object | |
soils | array of object | |
OverlaySection object
Sections object
basics | any | |
assessment | any | |
tax | any | |
sales | any | |
zoning | any | |
overlays | OverlaySection | |
ParcelHead object
parcel_uid | ParcelUid | |
status | current | |
publicgis_url | string | |
jurisdiction_id | string | |
county_name | string | |
state | string or null | |
apn | string | |
ParcelReport object
ParcelOverlays object
SupersededParcel object
parcel_uid | ParcelUid | |
status | superseded | |
superseded_by | array of ParcelUid | |
publicgis_url | string | |
ParcelEnvelope object
data | ParcelReport or SupersededParcel | |
OverlaysEnvelope object
data | ParcelOverlays or SupersededParcel | |
Resource object
One official page from GISFinder's directory, with GISFinder's verification status
resource_id | Text | |
name | string | |
description | Text | |
url | string | |
topics | array of string | |
jurisdiction_id | Text | |
publisher | Text | |
official | boolean | |
verified_status | one of verified, not_yet_verified, possibly_broken, None | |
verified_at | Instant | |
license_class | LicenseClass | |
page_url | string or null | |
OfficialResources object
parcel_uid | ParcelUid | |
jurisdiction_id | string | |
topic | Topic | |
provider | GISFinder | |
place | object or null | The smallest jurisdiction GISFinder resolved for the parcel's point |
resources | array of Resource | |
ResourcesEnvelope object
data | OfficialResources or SupersededParcel | |
AreaParcel object
A parcel summary. Never carries owner names or mailing addresses.
parcel_uid | ParcelUid | |
publicgis_url | string | |
jurisdiction_id | string | |
county_name | string | |
state | string or null | |
apn | string | |
acres_reported | Value | |
acres_mapped | Value | |
land_use_class | Value | |
land_use_class_2 | Value | |
land_use_class_3 | Value | |
situs_address | Value | |
situs_city | Value | |
situs_zip | Value | |
center | Geometry | |
AreaResult object
count | integer | Parcels matching the whole query |
parcels | array of AreaParcel | |
next_cursor | string or null | |
AreaEnvelope object
CountyCoverage object
jurisdiction_id | string | |
county_name | string | |
state | string or null | |
web_covered | boolean | |
api_cleared | boolean | The county's parcel source is licensed open or public_record, so the API serves it |
source | Source or null | |
as_of | Instant | |
AreaCoverage object
jurisdiction_id | string | |
name | string | |
web_covered | boolean | |
api_cleared | boolean | |
counties | array of CountyCoverage | |
as_of | Instant | |
ServedCoverage object
state | string or null | |
count | integer | |
counties | array of CountyCoverage | |
CoverageEnvelope object
data | ServedCoverage or CountyCoverage or AreaCoverage | |
Problem object
RFC 9457. type is one of https://www.publicgis.com/problems/ invalid-request, invalid-key, not-found, no-match, license-not-cleared, method-not-allowed, rate-limited, not-yet-open, unavailable, server-error.
type | one of https://www.publicgis.com/problems/invalid-request, https://www.publicgis.com/problems/invalid-key, https://www.publicgis.com/problems/not-found, https://www.publicgis.com/problems/no-match, https://www.publicgis.com/problems/license-not-cleared, https://www.publicgis.com/problems/method-not-allowed, https://www.publicgis.com/problems/rate-limited, https://www.publicgis.com/problems/not-yet-open, https://www.publicgis.com/problems/unavailable, https://www.publicgis.com/problems/server-error | |
title | string | |
status | integer | |
detail | string | |
instance | string | |
request_id | string | |
coverage | CountyCoverage or null | |