MapWise API
Programmatic access to Florida property data across all 67 counties — search parcels by owner, PIN, address, acreage, sales, value, and more. Every input is public record, sourced directly from the county property appraisers and the Florida Department of Revenue, with zero licensed or third-party-aggregated data. This API is for targeted queries and integrations, not bulk data download.
Quick Start
https://maps.mapwise.com/api_v2
Get started in 3 steps
- Try the test endpoint — no API key needed. Make a request to
/parcels/testto see the response format. - Get your API key — keys come with a paid MapWise Pro subscription that has a payment method on file; on a free trial, you can create one once the trial converts to paid. The account owner or billing admin logs in and opens My Account → Parcels API, then creates a key.
- Query the full endpoint — use your API key with
/parcelsto access all 67 Florida counties.
Authentication
There are two kinds of key, and they are not interchangeable.
| Key | What it can reach | Where it goes |
|---|---|---|
Secret key Server-only
a1b2c3…
|
Reads data: parcels, and anything else listed in the rates below. Not map tiles — the tile routes refuse a secret key. | Your server. It works from anywhere, so anyone who gets a copy can use it until you revoke it. Never put it in a web page, a mobile app, or a repository. |
Publishable key Safe to embed
mw_pk_…
|
Draws map tiles, and nothing else. It cannot read data. | Your web pages, where visitors can see it. It works only on the websites you register with it, so list just the sites you use. |
Both keys bill to your account. MapWise Pro includes API access, but usage is metered, not included: everything either key reads or draws is charged at the rates below. The difference between the two keys is reach, not cost — a secret key can read every product you pay for, from anywhere, while a publishable key can only draw tiles, and only from the sites you listed. Keep that list tight and watch your usage on Usage & spend.
Send a secret key on every request to https://maps.mapwise.com/api_v2, either way:
X-API-Key: YOUR_API_KEYAuthorization: Bearer YOUR_API_KEY
Sending the wrong class is refused rather than downgraded: the response is
403 with KEY_CLASS_MISMATCH. A publishable key cannot read data,
and a secret key is not accepted from a browser.
Create either kind in My Account → Parcels API. API access is included with MapWise Pro; issuing keys needs the Super Admin role.
Data & Pricing
API access is included with MapWise Pro — there is no separate API plan. You pay for what you use, billed monthly:
| Product | Rate | What it covers |
|---|---|---|
| Parcels | $0.06 / record | Parcel lookups, base data |
These are standard rates. Your account’s own rates, including any negotiated price, are shown under Usage & spend in My Account. For volume pricing, contact us.
- County coverage: all 67 Florida counties with complete sales history and property records.
- Data updates: weekly to biweekly from official county property appraiser sources (statewide median under a week).
- Test endpoint: free — requests to
/parcels/testdo not count toward billing.
Monthly Spend Caps
Every account includes a monthly spend cap — $100/month by default. Adjust it from
My Account → Parcels API → Monthly Spend Cap (requires the Super Admin or Billing role),
up to the account maximum of $1,000/month; for higher limits,
contact us about enterprise pricing. Once the cap is reached, further billable
requests are declined with 429 until usage resets on the 1st of the month (00:00 UTC).
Email warnings send at 50%, 80%, and 100% of the cap.
Security note: a leaked key cannot raise its own cap — spend-cap changes require an authenticated dashboard session, not the API key.
Every billable response includes your current spend state in headers:
X-Billing-Period-Spend-Cents: 1200
X-Billing-Period-Cap-Cents: 3000
X-Billing-Period-Reset: 2026-08-01T00:00:00Z
A cap-exceeded response is a 429, like a rate limit, but not the envelope every
other failure uses — it is its own shape, with no errormsg to read. Check for
a top-level error to tell the two apart:
{
"error": "spend_cap_exceeded",
"endpoint": "parcels",
"current_spend_cents": 10000,
"cap_cents": 10000,
"reset_at": "2026-10-01T00:00:00Z",
"estimated_call_cost_cents": 6,
"request_id": "01a0ba0a-e8f4-7fff-853c-bbce832d6c46"
}
It carries Retry-After set to the period reset, which may be days away, so treat it
differently from a rate limit: backing off will not help before reset_at. Either raise the
cap in My Account or wait for the reset. current_spend_cents against cap_cents
tells you which.
Rate Limiting & Security
Limits are per API key, and map tiles are counted separately from data so that panning a map cannot exhaust the budget your data calls need:
- Data endpoints: 10 requests per second, sustained. The allowance refills continuously, and you can spend up to 600 requests at once before it does — enough for a burst of parallel lookups, after which you are held to the sustained rate.
- Map tiles and WMS: 60 requests per second. A single pan or zoom fetches dozens of tiles, so these get their own, faster budget.
- The keyless
/parcels/testendpoint: 5 requests per second for anonymous callers. Use a key and the full/parcelsendpoint for the limits above.
Every response tells you where you stand, so a client can pace itself rather than guess:
X-RateLimit-Bucket-Capacity: 600
X-RateLimit-Bucket-Remaining: 587
X-RateLimit-Endpoint-Limit: 60
Over the limit is a 429 with a Retry-After header saying when to come
back. A 429 can also mean your monthly spend cap is reached rather than your rate
limit — the body’s error field says which, and
spend_cap_exceeded means waiting will not help until the period resets. Back off
exponentially either way; do not retry in a tight loop.
Need more throughput? Contact us — these are defaults, not ceilings.
Security best practices:
- Store API keys in environment variables — never commit them to version control.
- Rotate API keys regularly and use HTTPS for all requests.
- Monitor your API usage and rate limits.
Parcels
Test parcel search (no API key required)
/parcels/testTry the request and response format without an API key. Nothing is billed. Every search runs against Alachua County, whatever county you send, and is answered as an anonymous caller: the owner, values, sale prices, zoning and land use come back masked (***, or a redacted object). Send the same query to /parcels with a key for the full record.
Returns matching Alachua County parcels, with the owner, values and sale prices masked. Anything else is a refusal — see Error Responses.
Query parameters
| Parameter | Type | Description |
|---|---|---|
searchCounty | string | County name (case- and separator-insensitive; ALL for statewide). |
searchPin | string | Parcel identification number (supports * wildcard). |
searchParcelId | string | Parcel ID (supports * wildcard). |
limit | integer | Page size, 1 to 100. Values above 100 are capped at 100. |
offset | integer | Row offset for pagination. Ignored when start is also sent. |
Response 200
successbooleandataarray of objectmetaobjectogc_fidintegercountystringextracted_atstringWhen this parcel was last extracted from the source.countintegerpa_pin_linkobjectA field withheld from keyless requests. Send an API key to receive it.redactedbooleanmessagestringidentifiersobjectpinstringpin_cleanstringpin2string nullablepin2_cleanstring nullablealtkeystring nullableprop_detailsstring nullableownerobjectA field withheld from keyless requests. Send an API key to receive it.redactedbooleanmessagestringsiteobjectaddressstring nullablecitystring nullablezipcodestring nullablesubdivisionstring nullablesubdivision_commonstring nullablesubdivision_idstring nullablecondostring nullableWithheld from keyless requests: always *** (or null where the record has no value).landobjectacres_deednumber nullableacres_gisnumber nullablezoningstring nullableWithheld from keyless requests: always *** (or null where the record has no value).land_useobjectA field withheld from keyless requests. Send an API key to receive it.redactedbooleanmessagestringbuildingobjectnum_buildingsinteger nullablestoriesnumber nullablesqftobjectheatednumber nullabletotalnumber nullableadjustednumber nullabletotal_sqftnumber nullableheated_sqftnumber nullableyear_built_actualinteger nullableyear_built_effectiveinteger nullablebedsinteger nullablebathsobjectfullinteger nullablehalfinteger nullablevaluationobjectmarketobjectbuildingstring nullableWithheld from keyless requests: always *** (or null where the record has no value).improvementsstring nullableWithheld from keyless requests: always *** (or null where the record has no value).landstring nullableWithheld from keyless requests: always *** (or null where the record has no value).agstring nullableWithheld from keyless requests: always *** (or null where the record has no value).totalstring nullableWithheld from keyless requests: always *** (or null where the record has no value).assessed_totalstring nullableWithheld from keyless requests: always *** (or null where the record has no value).exempt_totalstring nullableWithheld from keyless requests: always *** (or null where the record has no value).taxable_totalstring nullableWithheld from keyless requests: always *** (or null where the record has no value).salesobjectrecentobject nullabledatestring nullableSale dates are not masked.amountstring nullableWithheld from keyless requests: always *** (or null where the record has no value).typestring nullableWithheld from keyless requests: always *** (or null where the record has no value).qualstring nullableWithheld from keyless requests: always *** (or null where the record has no value).vacstring nullableWithheld from keyless requests: always *** (or null where the record has no value).bookstring nullableWithheld from keyless requests: always *** (or null where the record has no value).pagestring nullableWithheld from keyless requests: always *** (or null where the record has no value).docnumstring nullablegrantorstring nullablepreviousarray of objectdatestring nullableSale dates are not masked.amountstring nullableWithheld from keyless requests: always *** (or null where the record has no value).typestring nullableWithheld from keyless requests: always *** (or null where the record has no value).qualstring nullableWithheld from keyless requests: always *** (or null where the record has no value).vacstring nullableWithheld from keyless requests: always *** (or null where the record has no value).bookstring nullableWithheld from keyless requests: always *** (or null where the record has no value).pagestring nullableWithheld from keyless requests: always *** (or null where the record has no value).docnumstring nullablegrantorstring nullablemetaobjectrecord_countintegerRecords returned in this page.total_countintegerTotal matching records.test_modebooleandata_scopeobjectcountystringErrors
| Status | Meaning |
|---|---|
400 | Invalid parameter. |
429 | Too many requests. This endpoint is limited more tightly than the keyed ones; back off and retry. |
500 | Unexpected server failure. |
Search parcels
/parcelsFull parcel search across all 67 counties. Requires an API key. Billed per record returned. Combine spatial, owner, value, and characteristic filters; paginate with limit/offset.
Returns matching parcels. Anything else is a refusal — see Error Responses.
Query parameters
| Parameter | Type | Description |
|---|---|---|
format | string | Response format (case-insensitive). enum: JSON · HTML |
limit | integer | Page size, 1 to 100. Values above 100 are capped at 100. |
start | integer | Row offset, and an alias of offset. Send both and start wins. |
offset | integer | Row offset for pagination. Ignored when start is also sent. |
sortBy | string | Field to sort by. enum: owner · county · pin · acres · sale_date |
order | string | Sort direction. enum: ASC · DESC |
searchCounty | string | County name (case- and separator-insensitive; ALL for statewide). |
searchCountyMulti | string | Comma-separated list of counties (each validated against the county list; ALL accepted; case- and separator-insensitive). NOTE: when combined with searchCounty, the two are AND-ed — sending both narrows to the intersection and disjoint sets return an empty 200. (Behavior tracked in api_v2#1602.) |
searchOwner | string | Owner name (partial match). |
searchMailing | string | Owner mailing address (partial match). |
searchPin | string | Parcel identification number (supports * wildcard). |
searchParcelId | string | Parcel ID (supports * wildcard). |
searchAddress | string | Site street address (partial match). |
searchNumber | string | Site street number. |
searchStreet | string | Site street name. |
searchUnit | string | Site unit. |
searchCity | string | Site city. |
searchZipcode | string | Site ZIP (5-digit or ZIP+4). |
searchCityOwner | string | Owner-address city. |
searchStateOwner | string | Owner-address state. |
searchZipcodeOwner | string | Owner-address ZIP (5-digit or ZIP+4). |
searchAcresMin | number | |
searchAcresMax | number | |
saleAmountMin | number | |
saleAmountMax | number | |
saleDateMin | string <date> | Earliest sale date (YYYY-MM-DD). |
saleDateMax | string <date> | Latest sale date (YYYY-MM-DD). |
searchSqftHtdMin | number | |
searchSqftHtdMax | number | |
searchSqftTotMin | number | |
searchSqftTotMax | number | |
searchSqftAdjMin | number | |
searchSqftAdjMax | number | |
searchYearBuiltMin | integer | |
searchYearBuiltMax | integer | |
searchYearBuiltEffMin | integer | |
searchYearBuiltEffMax | integer | |
searchMarketValueMin | number | |
searchMarketValueMax | number | |
searchLandUse | string | DOR land-use code(s), comma-separated. |
searchLandUse4 | string | 4-digit land-use code(s), comma-separated. |
searchSubdivision | string | |
searchLegal | string | Legal description (partial match). |
searchBook | string | |
searchPage | string | |
searchDocnum | string | |
bbox | string | Bounding box xmin,ymin,xmax,ymax (EPSG:4326). |
lat_lon | string | Point "lat,lon" for point-in-parcel search. |
searchGeometry | string | WKT geometry for spatial search (GeoJSON via POST body). |
bufferPoint | boolean | Treat lat_lon as the center of a buffer. |
bufferMeters | integer | Buffer radius in METRES (1-50000; 1 mile ~ 1609). |
includeGeometry | boolean | Include parcel geometry in the response. |
includeLegal | boolean | Include the legal description. |
includeHazards | boolean | Include hazard (flood) data. |
includeFloodZoneFragments | boolean | Include per-zone flood fragments. |
Response 200
successbooleandataarray of objectmetaobjectogc_fidintegercountystringextracted_atstringWhen this parcel was last extracted from the source.countintegerpa_pin_linkstringLink to the county property appraiser record.identifiersobjectpinstringpin_cleanstringpin2string nullablepin2_cleanstring nullablealtkeystring nullableprop_detailsstring nullableownerobjectprimary_namestringsecondary_namestring nullabletertiary_namestring nullableaddress_line1stringaddress_line2string nullableaddress_line3string nullablecitystringstatestringzipcodestringsiteobjectaddressstring nullablecitystring nullablezipcodestring nullablesubdivisionstring nullablesubdivision_commonstring nullablesubdivision_idstring nullablecondostring nullablelandobjectacres_deednumber nullableacres_gisnumber nullablezoningstring nullableland_useobjectlusestring nullableluse_descstring nullablelusedorstring nullablelusedor_descstring nullablebuildingobjectnum_buildingsinteger nullablestoriesnumber nullablesqftobjectheatednumber nullabletotalnumber nullableadjustednumber nullabletotal_sqftnumber nullableheated_sqftnumber nullableyear_built_actualinteger nullableyear_built_effectiveinteger nullablebedsinteger nullablebathsobjectfullinteger nullablehalfinteger nullablevaluationobjectmarketobjectbuildinginteger nullableimprovementsinteger nullablelandinteger nullableaginteger nullabletotalinteger nullableassessed_totalinteger nullableexempt_totalinteger nullabletaxable_totalinteger nullablesalesobjectrecentobject nullabledatestring nullableamountinteger nullabletypestring nullablequalstring nullablevacstring nullablebookstring nullablepagestring nullabledocnumstring nullablegrantorstring nullablepreviousarray of objectdatestring nullableamountinteger nullabletypestring nullablequalstring nullablevacstring nullablebookstring nullablepagestring nullabledocnumstring nullablegrantorstring nullablemetaobjectrecord_countintegerRecords returned in this page.total_countintegerTotal matching records.Errors
| Status | Meaning |
|---|---|
400 | Invalid parameter. |
401 | Missing or invalid API key. |
403 | Wrong key class, or a website the key does not list. A publishable key cannot read data (code KEY_CLASS_MISMATCH). |
429 | Rate limit or spend cap exceeded. These are two different bodies: a rate limit is the standard error envelope, a spend cap is its own shape carrying the billing state you need to recover. Check for a top-level error of spend_cap_exceeded to tell them apart. |
500 | Unexpected server failure. |
The keyless /parcels/test endpoint accepts every parameter, but results are limited to a fixed scope:
- County: ALACHUA only — other counties return empty results.
- City: GAINESVILLE only for site-address searches.
- Sale dates: 1990-01-01 to 2000-12-31 only.
Use the full /parcels endpoint with your API key for complete access.
Error Responses
Every failure has the same shape:
{
"success": false,
"status": 400,
"timestamp": "2026-09-20 15:56:09.032819",
"errormsg": "Query validation failed.",
"errors": [
"Parameter 'limit' has an invalid format (received 'abc').",
"Missing required parameter: searchCounty"
],
"help": { "message": "For a complete list of valid parameters..." }
}
A 400 carries an errors array with one entry per failed rule and a
help object pointing at the parameters involved. Other failures carry the same four
fields with no errors.
Where a refusal has a machine-readable reason, branch on that rather than on
errormsg, whose wording is written for a person and can change. A request a key
gate refused carries it at data.code — KEY_CLASS_MISMATCH when a key of
the wrong class is used — and a path that does not route carries it at the top level.
Every response carries an X-Request-Id, including failures. It identifies
that one request in our logs, so include it when you contact us and we can find what happened instead of
searching. Send your own if you would rather correlate with your logs: up to 64 letters, digits and
hyphens, and we will use yours instead of ours — so send something unique per request.
| Status | Description | Common cause |
|---|---|---|
400 | Bad Request | Missing searchCounty, lat_lon, or bbox |
401 | Unauthorized | Missing or invalid API key |
404 | Not Found | No such endpoint path, with a valid key — without one you get 401 first, because the key is checked before the path. A search that matches nothing is not a 404: it is a 200 with an empty array and meta.record_count of 0, so do not wait for a status that will never arrive. |
403 | Forbidden | A publishable key was sent to a data endpoint, or a secret key to a browser one — the body's code is KEY_CLASS_MISMATCH. Keys are not interchangeable; use the class the endpoint takes. |
403 | Forbidden | Origin not permitted for this API key — the request came from a website the key does not list, or carried no Origin and no Referer at all. Add the site in My Account, and note that a server-side call cannot use a publishable key. |
429 | Too Many Requests | Rate limit (10 requests per second on data endpoints, 60 per second on tiles) or monthly spend cap reached — check the body's error field: spend_cap_exceeded means wait for the period reset, anything else means back off and retry |
500 | Internal Server Error | Unexpected server failure |
Best Practices
- Validate all user-supplied input before sending requests.
- Check
meta.record_countto determine if any results were returned. - Handle non-200 HTTP status codes gracefully in your application.
- Implement exponential backoff when you receive a
429response. - All error responses include
success: falseand anerrormsgfield.
Contact & Support
Questions about the API, higher rate limits, or enterprise pricing? Contact us.