MapWise API Reference

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

Base URL

https://www.mapwise.com/api_v2

  1. Get a secret key. Open My Account → MapWise API and choose Generate new secret key. Copy the key straight away: it is shown once. Authentication covers who can create keys and how to store and send them.
  2. Make a first request. From a terminal or your server, run a small search:
    curl "https://www.mapwise.com/api_v2/parcels?searchCounty=ALACHUA&limit=1" \
      -H "X-API-Key: YOUR_API_KEY"
    Replace YOUR_API_KEY with your key. Searches are billed per record returned, at the rate in Data & Pricing. To see the response format first, with no key and no charge, send the same query to /parcels/test, which answers with masked Alachua County records.
  3. Check the response. Success is HTTP 200 with "success": true, the matching parcels in data, and how many came back in meta.record_count. The full response is shown under Search parcels. A 401 means the key was missing or wrong; every other failure is listed in Error Responses.
  4. Where to go next. See Parcels for every search parameter, Rate Limiting and Monthly Spend Caps for the limits on your key, and Rotating a Key before you deploy.

Authentication

There are two kinds of key, and they are not interchangeable.

KeyWhat it can reachWhere it goes
Secret key Server-only mw_sk_… 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.

Managing secret keys

A secret key starts with mw_sk_. Keys issued before that format are 64 hex characters and keep working unchanged. In My Account they show a bare last four characters, and the oldest, made before any of the key was recorded for display, are listed as Legacy key.

Who can create keys. 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. Creating, renaming and revoking keys needs the Super Admin or Billing role. When a request is refused, the dashboard shows why; the reasons are listed under Error Responses.

  • Shown once. The full key appears only when you create it. MapWise stores a hash, not the key, so it cannot show it again: afterwards the list shows its label and, for an mw_sk_ key, its last four characters. If you lose a key, create a new one and revoke the old.
  • Several keys. Each user can hold up to 10 live secret keys at a time. Give each integration its own key, with a label saying where it runs, so revoking one does not break the others. Labels can be renamed; the key itself does not change. At the limit, creating another is refused with LIMIT_REACHED: revoke one you no longer use.
  • Revoking. Revoke a key from the same list. It stops working on the next request, answering 401, and cannot be restored. You can revoke your last key and create another later.
  • Security email. Whenever a key, secret or publishable, is created or revoked, MapWise sends a security email to the key’s owner and to every other user on the account who can manage keys. It names the key, who acted and when. It cannot be turned off. It is best-effort: if it cannot be sent, the key action still succeeds, so the list in My Account remains the record of your keys. If you did not expect one, revoke the key and contact us.

Rotating a key

There is no rotate button. You rotate by running two keys side by side for a moment, so no request fails in between:

  1. Create a new secret key and store it wherever your integration reads its secrets.
  2. Switch your integration to the new key, and confirm its requests succeed.
  3. Revoke the old key in My Account.

Rotate when someone who had the key leaves, when it may have been exposed, or on the schedule your own security policy sets. If a key has leaked, revoke it first rather than waiting for the switch.

Sending a key

Send a secret key on every data request to https://www.mapwise.com/api_v2, either way:

  • X-API-Key: YOUR_API_KEY
  • Authorization: Bearer YOUR_API_KEY

Keep a secret key on your server, in an environment variable or a secrets manager. Never put it in a browser page, a mobile app or a repository: anything shipped to a device can be read by its user, and a key committed to a repository stays in its history after you delete the file. Do not put it in a URL either, where it ends up in logs.

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.

A publishable key goes in the query string instead, as ?apiKey=mw_pk_…, from a page on one of the websites you registered. Map libraries send it that way for you; see Map Tiles. Never send it in a header: a browser will not send X-API-Key to another site.

Create either kind in My Account → MapWise API.

Data & Pricing

API access is included with MapWise Pro. There is no separate API plan. You pay for what you use, billed monthly:

ProductRateWhat it covers
Parcels$0.06 / recordParcel lookups, base data
Tiles$0.50 / 1,000 tilesTile blocks (1,000 tiles per unit)

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/test do not count toward billing.

Monthly Spend Caps

Every account includes a monthly spend cap, $100/month by default. Adjust it from My Account → MapWise 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

Each endpoint draws on one of two kinds of bucket, and the table shows which:

  • account is one budget shared across all the keys on your account. It refills at the sustained rate and allows a burst: you can spend up to the burst at once, for a run of parallel lookups, after which you are held to the sustained rate.
  • endpoint is a separate budget for that one endpoint, so a busy endpoint cannot use up what another needs. Map tiles have their own, because a single pan or zoom fetches dozens of tiles.
EndpointBucketSustainedBurstPer minutePer day
GET /parcelsaccount10/s600--
GET /parcels/testendpoint5/s---
GET /tilesaccount10/s600--
GET /tiles/{tileset}/tiles.jsonendpoint60/s---
GET /tiles/{tileset}/{z}/{x}/{y}.jpgendpoint60/s-600/min100,000/day

Every response tells you where you stand, so a client can pace itself rather than guess:

HeaderMeaning
X-RateLimit-Bucket-CapacityYour burst allowance: the most requests your bucket holds.
X-RateLimit-Bucket-Refill-RateRequests per second added back to your bucket.
X-RateLimit-Bucket-RemainingRequests left in your bucket now.
X-RateLimit-Endpoint-LimitRequests this endpoint allows per window, counted per account (or per IP address without a key).
X-RateLimit-Endpoint-RemainingRequests left in the current window.
X-RateLimit-Endpoint-ResetWhen the current window ends, as a Unix timestamp in seconds.
X-RateLimit-Global-LimitRequests per minute the service accepts across all callers. A shared safeguard, not your own allowance.
X-RateLimit-Global-RemainingRequests left in the shared minute.
X-RateLimit-Global-ResetWhen the shared minute ends, as a Unix timestamp in seconds.

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.
  • Use HTTPS for all requests.
  • Monitor your API usage and rate limits.

Parcels

Search parcels

GET /parcels

Full 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

ParameterTypeDescription
format stringResponse format. Case-insensitive.
enum: JSON · HTML
limit integerPage size, 1 to 100. Reduce the page size and paginate with offset.
start DeprecatedintegerRow offset, and an alias of offset. Send both and start wins. Deprecated: use offset instead.
offset integerRow offset for pagination. Ignored when start is also sent.
order stringSort direction. Case-insensitive.
enum: ASC · DESC
dir DeprecatedstringSort direction. Deprecated: use order instead. Case-insensitive.
enum: ASC · DESC
sortBy stringField to sort by. Case-insensitive.
enum: owner · county · pin · acres · sale_date
sort DeprecatedstringField to sort by. Deprecated: use sortBy instead. Case-insensitive.
enum: owner · county · pin · acres · sale_date
bufferPoint booleanTreat lat_lon as the center of a buffer. Also accepts 1/0 and yes/no.
bufferMeters integerUsed with bufferPoint. Buffer radius in METRES (1-50000). 1 mile ~ 1609, 1 foot ~ 0.3.
includeGeometry booleanInclude parcel geometry in the response. Also accepts 1/0 and yes/no.
includeLegal booleanInclude the legal description. Also accepts 1/0 and yes/no.
geometry DeprecatedbooleanOlder spelling of includeGeometry. Deprecated: use includeGeometry instead. Also accepts 1/0 and yes/no.
searchCounty stringCounty name; ALL for statewide. Case-insensitive. Spaces, underscores and hyphens are interchangeable.
enum: ALACHUA · BAKER · BAY · BRADFORD · BREVARD · BROWARD · CALHOUN · CHARLOTTE · CITRUS · CLAY · COLLIER · COLUMBIA · DESOTO · DIXIE · DUVAL · ESCAMBIA · FLAGLER · FRANKLIN · GADSDEN · GILCHRIST · GLADES · GULF · HAMILTON · HARDEE · HENDRY · HERNANDO · HIGHLANDS · HILLSBOROUGH · HOLMES · INDIAN_RIVER · JACKSON · JEFFERSON · LAKE · LAFAYETTE · LEE · LEON · LEVY · LIBERTY · MADISON · MANATEE · MARION · MARTIN · MIAMI-DADE · MONROE · NASSAU · OKALOOSA · OKEECHOBEE · ORANGE · OSCEOLA · PALM_BEACH · PASCO · PINELLAS · POLK · PUTNAM · ST_JOHNS · ST_LUCIE · SANTA_ROSA · SARASOTA · SEMINOLE · SUMTER · SUWANNEE · TAYLOR · UNION · VOLUSIA · WAKULLA · WALTON · WASHINGTON · ALL
searchCountyMulti stringSeveral counties, 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.) Comma-separated list. Allowed values: ALACHUA, BAKER, BAY, BRADFORD, BREVARD, BROWARD, CALHOUN, CHARLOTTE, CITRUS, CLAY, COLLIER, COLUMBIA, DESOTO, DIXIE, DUVAL, ESCAMBIA, FLAGLER, FRANKLIN, GADSDEN, GILCHRIST, GLADES, GULF, HAMILTON, HARDEE, HENDRY, HERNANDO, HIGHLANDS, HILLSBOROUGH, HOLMES, INDIAN_RIVER, JACKSON, JEFFERSON, LAKE, LAFAYETTE, LEE, LEON, LEVY, LIBERTY, MADISON, MANATEE, MARION, MARTIN, MIAMI-DADE, MONROE, NASSAU, OKALOOSA, OKEECHOBEE, ORANGE, OSCEOLA, PALM_BEACH, PASCO, PINELLAS, POLK, PUTNAM, ST_JOHNS, ST_LUCIE, SANTA_ROSA, SARASOTA, SEMINOLE, SUMTER, SUWANNEE, TAYLOR, UNION, VOLUSIA, WAKULLA, WALTON, WASHINGTON, ALL.
searchOwner stringOwner name (partial match).
searchMailing stringOwner mailing address (partial match).
searchPin stringParcel identification number (supports * wildcard).
searchParcelId stringParcel ID (supports * wildcard).
searchCity stringSite city.
searchCityOwner stringOwner-address city.
searchStateOwner stringOwner-address state.
searchZipcodeOwner stringOwner-address ZIP (5-digit or ZIP+4).
searchAddress stringSite street address (partial match).
searchNumber stringSite street number.
searchStreet stringSite street name.
searchUnit stringSite unit.
searchAcresMin numberSmallest parcel size, in acres.
searchAcresMax numberLargest parcel size, in acres.
searchAcres1 DeprecatednumberOlder spelling of searchAcresMin. Deprecated: use searchAcresMin instead.
searchAcres2 DeprecatednumberOlder spelling of searchAcresMax. Deprecated: use searchAcresMax instead.
saleAmountMin numberLowest sale price, in dollars.
saleAmountMax numberHighest sale price, in dollars.
saleDateMin string <date>Earliest sale date (YYYY-MM-DD).
saleDateMax string <date>Latest sale date (YYYY-MM-DD).
lat_lon stringPoint "lat,lon" for point-in-parcel search.
bbox stringBounding box xmin,ymin,xmax,ymax (EPSG:4326).
searchZipcode stringSite ZIP (5-digit or ZIP+4).
searchSqftHtdMin numberSmallest heated area, in square feet.
searchSqftHtdMax numberLargest heated area, in square feet.
searchSqftTotMin numberSmallest total building area, in square feet.
searchSqftTotMax numberLargest total building area, in square feet.
searchSqftAdjMin numberSmallest adjusted building area, in square feet.
searchSqftAdjMax numberLargest adjusted building area, in square feet.
searchYearBuiltMin integerEarliest actual year built.
searchYearBuiltMax integerLatest actual year built.
searchYearBuiltEffMin integerEarliest effective year built.
searchYearBuiltEffMax integerLatest effective year built.
searchMarketValueMin numberLowest total market value, in dollars.
searchMarketValueMax numberHighest total market value, in dollars.
searchLandUse stringDOR land-use code(s), comma-separated.
searchLandUse4 string4-digit land-use code(s), comma-separated.
searchSubdivision stringSubdivision name (partial match).
searchLegal stringLegal description (partial match). Format: ^[\p{L}\p{N}\s\-_\.,#*@&\/:'"()]+$, Unicode.
searchBook stringOfficial records book of the sale.
searchPage stringOfficial records page of the sale.
searchDocnum stringOfficial records document number of the sale.
searchGeometry stringWKT geometry for spatial search (GeoJSON via POST body).

Response 200

successboolean
statusinteger
timestampstringWhen the response was generated, server time.
dataarray of object
metaobject
ogc_fidinteger
countystring
extracted_atstringWhen this parcel was last extracted from the source.
countinteger
pa_pin_linkstringLink to the county property appraiser record.
legal_match_scorenumberHow closely the legal description matched, 0 to 1. Present only on an approximate legal match (meta.legal_match is approximate).
geometryobjectPresent when includeGeometry is true.
geojsonstring nullableThe parcel outline as a GeoJSON MultiPolygon, serialized as a string. Coordinates are EPSG:3857 (Web Mercator), named in its crs member.
latnumber nullableLatitude of the parcel centroid, EPSG:4326.
lonnumber nullableLongitude of the parcel centroid, EPSG:4326.
identifiersobject
pinstring
pin_cleanstring
pin2string nullable
pin2_cleanstring nullable
altkeystring nullable
prop_detailsstring nullable
ownerobject
primary_namestring
secondary_namestring nullable
tertiary_namestring nullable
address_line1string
address_line2string nullable
address_line3string nullable
citystring
statestring
zipcodestring
siteobject
addressstring nullable
citystring nullable
zipcodestring nullable
subdivisionstring nullable
subdivision_commonstring nullable
subdivision_idstring nullable
condostring nullable
landobject
acres_deednumber nullable
acres_gisnumber nullable
zoningstring nullable
land_useobject
lusestring nullable
luse_descstring nullable
lusedorstring nullable
lusedor_descstring nullable
buildingobject
num_buildingsinteger nullable
storiesnumber nullable
sqftobject
heatednumber nullable
totalnumber nullable
adjustednumber nullable
total_sqftnumber nullable
heated_sqftnumber nullable
year_built_actualinteger nullable
year_built_effectiveinteger nullable
bedsinteger nullable
bathsobject
fullinteger nullable
halfinteger nullable
valuationobject
marketobject
buildinginteger nullable
improvementsinteger nullable
landinteger nullable
aginteger nullable
totalinteger nullable
assessed_totalinteger nullable
exempt_totalinteger nullable
taxable_totalinteger nullable
salesobject
recentobject nullable
datestring nullable
amountinteger nullable
typestring nullable
qualstring nullable
vacstring nullable
bookstring nullable
pagestring nullable
docnumstring nullable
grantorstring nullable
qualificationobject nullableMapWise's reading of qual against the Florida DOR sale-qualification codes. Null when qual is empty or unrecognised, or when it is a numeric code on a sale dated before 2009-01-01 or undated (before the statewide codes, counties used their own lists).
codestringA DOR two-digit code, or Q/U where the county states only a verdict.
statusstringOne of: qualified (DOR 01–06, Q), disqualified (11–21, 30–43, U), pending (98, 99). Extensible: treat an unknown value as unknown.
reasonstring nullableShort plain-language meaning of the DOR code; null for Q/U.
vacant_improvedstring nullablevacant or improved, from vac where the county's word is unambiguous; null otherwise.
type_labelstring nullablePlain-language deed instrument for type; null when unknown or a numeric county code.
or_urlstring nullableLink to the county Clerk's Official Records document for this sale, where the county is supported and the sale has a book/page or document number. A third-party site, provided as-is; it may change.
previousarray of object
datestring nullable
amountinteger nullable
typestring nullable
qualstring nullable
vacstring nullable
bookstring nullable
pagestring nullable
docnumstring nullable
grantorstring nullable
qualificationobject nullableMapWise's reading of qual against the Florida DOR sale-qualification codes. Null when qual is empty or unrecognised, or when it is a numeric code on a sale dated before 2009-01-01 or undated (before the statewide codes, counties used their own lists).
codestringA DOR two-digit code, or Q/U where the county states only a verdict.
statusstringOne of: qualified (DOR 01–06, Q), disqualified (11–21, 30–43, U), pending (98, 99). Extensible: treat an unknown value as unknown.
reasonstring nullableShort plain-language meaning of the DOR code; null for Q/U.
vacant_improvedstring nullablevacant or improved, from vac where the county's word is unambiguous; null otherwise.
type_labelstring nullablePlain-language deed instrument for type; null when unknown or a numeric county code.
or_urlstring nullableLink to the county Clerk's Official Records document for this sale, where the county is supported and the sale has a book/page or document number. A third-party site, provided as-is; it may change.
legalobjectPresent when includeLegal is true.
fullstring nullableThe whole legal description.
legal1string nullableThe legal description as the county splits it, one part per field. Most counties fill only the first.
legal2string nullable
legal3string nullable
legal4string nullable
legal5string nullable
legal6string nullable
metaobject
record_countintegerRecords returned in this page.
total_countintegerTotal matching records.
legal_matchstringPresent when searchLegal was sent. approximate means no legal description contained the text, so the closest matches were returned instead, ranked by legal_match_score.
legal_match_cappedbooleanPresent with an approximate match: the results are one capped page, so paging does not apply.
sort_overriddenbooleanPresent with an approximate match: results are ordered by match score, not by sortBy.
legal_fallback_skippedstringPresent when no exact legal match was found and the approximate search did not run. term_too_short means the searchLegal text is shorter than legal_fallback_min_length.
legal_fallback_min_lengthintegerThe shortest searchLegal text the approximate search accepts, in characters.

Errors

StatusMeaning
400Invalid parameter.
401Missing or invalid API key.
403Wrong key class, or a website the key does not list. A publishable key cannot read data (code KEY_CLASS_MISMATCH).
429Rate 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.
500Unexpected server failure.

Test parcel search (no API key required)

GET /parcels/test

Try the request and response format without an API key. Nothing is billed. Searches only Alachua County; any other searchCounty is refused with a 400. Every search is answered as an anonymous caller: the owner, values, sale prices, building details, 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

ParameterTypeDescription
limit stringPage size.
start DeprecatedstringRow offset, and an alias of offset. Send both and start wins. Deprecated: use offset instead.
offset stringRow offset for pagination. Ignored when start is also sent.
searchCounty requiredstringCounty name. Only ALACHUA is accepted. Case-insensitive. Spaces, underscores and hyphens are interchangeable.
enum: ALACHUA
searchPin stringParcel identification number.
searchParcelId stringParcel ID.

Response 200

successboolean
statusinteger
timestampstringWhen the response was generated, server time.
dataarray of object
metaobject
ogc_fidinteger
countystring
extracted_atstringWhen this parcel was last extracted from the source.
countinteger
pa_pin_linkobjectA field withheld from keyless requests. Send an API key to receive it.
redactedboolean
messagestring
identifiersobject
pinstring
pin_cleanstring
pin2string nullable
pin2_cleanstring nullable
altkeystring nullable
prop_detailsstring nullable
ownerobjectA field withheld from keyless requests. Send an API key to receive it.
redactedboolean
messagestring
siteobject
addressstring nullable
citystring nullable
zipcodestring nullable
subdivisionstring nullable
subdivision_commonstring nullable
subdivision_idstring nullable
condostring nullableWithheld from keyless requests: always *** (or null where the record has no value).
landobject
acres_deednumber nullable
acres_gisnumber nullable
zoningstring 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.
redactedboolean
messagestring
buildingobject
num_buildingsstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
storiesstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
sqftobject
heatedstring 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).
adjustedstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
total_sqftstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
heated_sqftstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
year_built_actualstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
year_built_effectivestring nullableWithheld from keyless requests: always *** (or null where the record has no value).
bedsstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
bathsobject
fullstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
halfstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
valuationobject
marketobject
buildingstring 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).
salesobject
recentobject nullable
datestring 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 nullable
grantorstring nullable
qualificationobject nullable
codestring nullableWithheld from keyless requests: always *** (or null where the record has no value).
statusstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
reasonstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
vacant_improvedstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
type_labelstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
or_urlstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
previousarray of object
datestring 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 nullable
grantorstring nullable
qualificationobject nullable
codestring nullableWithheld from keyless requests: always *** (or null where the record has no value).
statusstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
reasonstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
vacant_improvedstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
type_labelstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
or_urlstring nullableWithheld from keyless requests: always *** (or null where the record has no value).
metaobject
record_countintegerRecords returned in this page.
total_countintegerTotal matching records.
legal_matchstringPresent when searchLegal was sent. approximate means no legal description contained the text, so the closest matches were returned instead, ranked by legal_match_score.
legal_match_cappedbooleanPresent with an approximate match: the results are one capped page, so paging does not apply.
sort_overriddenbooleanPresent with an approximate match: results are ordered by match score, not by sortBy.
legal_fallback_skippedstringPresent when no exact legal match was found and the approximate search did not run. term_too_short means the searchLegal text is shorter than legal_fallback_min_length.
legal_fallback_min_lengthintegerThe shortest searchLegal text the approximate search accepts, in characters.
test_modeboolean
data_scopeobject
countystring

Errors

StatusMeaning
400Invalid parameter.
403Owner-name search refused (searchOwner, documented on /parcels): an anonymous caller cannot search by owner (data.error_code PLUS_REQUIRED).
429Too many requests. This endpoint is limited more tightly than the keyed ones; back off and retry.
500Unexpected server failure.
Test endpoint data scope

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.

Map Tiles

MapWise aerial imagery as standard XYZ raster tiles, which every mainstream map library draws, including MapLibre GL JS, Leaflet and OpenLayers. Each tileset also has a TileJSON document, which MapLibre and OpenLayers read natively. Six Florida tilesets, zoom 4 to 21, drawn straight from your own web page. Every tile is a 256 px JPEG.

1. Get a publishable key and register your websites

Tiles take a publishable key (mw_pk_…) and nothing else; a secret key is refused. Create one under My Account → MapWise API and list the websites that will show the map (https://your-site.example, plus http://localhost:5173 while you develop). The key is origin-locked: it is safe in a web page, and browsers on other websites are refused. Outside a browser an Origin header can be forged, so treat the lock as the first line of defence, not the only one. You are responsible for every request made with your key; your monthly spend cap bounds what a misused key can cost, and you can revoke a key and create a new one at any time.

A registered host covers its subdomains, but the scheme and the port must match exactly. A key registered for http://localhost:5173 is refused on https://localhost:5173 and on http://localhost:5174 alike, so register each port you develop on.

Because subdomains are included, register the site you own, not a shared hosting domain above it. If your demo lives at https://your-team.github.io, register that, never https://github.io: the parent would let every site on that host draw tiles on your key. The same goes for netlify.app, vercel.app, pages.dev and similar hosts.

Send the key as the apiKey query parameter, the way every tile client expects. X-API-Key is not usable from a web page, since a browser's CORS preflight will not allow that header from another site, and the terms license tiles for display in browser pages only.

2. Add the source to your map

The tile template is the universal path: any library can draw https://www.mapwise.com/api_v2/tiles/{tileset}/{z}/{x}/{y}.jpg?apiKey=… directly. The TileJSON document is offered because it carries minzoom, maxzoom, bounds and attribution for you, and its tiles[] template already has your key in it: no request interception, no headers. MapLibre and OpenLayers read it natively; Leaflet has no TileJSON loader, so its sample uses the template.

Keep the attribution visible. Show it on the map or immediately next to it, and do not remove it or let your interface cover it. A corner of the map (lower-right is conventional) is the usual place; check it stays legible against the imagery, which is brighter and busier than most basemaps.

Each tileset's TileJSON carries the string, and the libraries that read the document display it for you. For aerials-latest it is Imagery: FDOT / FDOR / Florida counties · Tiles © MapWise, with each source linked.

MapLibre, Leaflet and OpenLayers render it as HTML. The TileJSON spec also permits a client to treat the field as plain text, so handle the markup if you render it yourself.

Our tiles are 256 px. MapLibre's raster default is 512, so set tileSize: 256 there or the imagery draws at the wrong scale.

const map = new maplibregl.Map({
  container: 'map',
  style: { version: 8, sources: {}, layers: [] },
  center: [-81.38, 28.54],
  zoom: 12,
});

map.on('load', () => {
  map.addSource('mapwise-aerials', {
    type: 'raster',
    url: 'https://www.mapwise.com/api_v2/tiles/aerials-latest/tiles.json?apiKey=mw_pk_YOUR_PUBLISHABLE_KEY',
    tileSize: 256,   // our tiles are 256 px; MapLibre's raster default is 512
  });
  map.addLayer({ id: 'aerials', type: 'raster', source: 'mapwise-aerials' });
});

// Attribution is required. MapLibre reads it from the TileJSON and shows it
// in the attribution control:
// Imagery: <a href="https://www.fdot.gov/">FDOT</a> / <a href="https://floridarevenue.com/">FDOR</a> / Florida counties · Tiles © <a href="https://www.mapwise.com/">MapWise</a>

To read the zoom range and footprint from the document rather than pasting them, in Leaflet: fetch(tileJsonUrl).then((r) => r.json()).then((tj) => L.tileLayer(tj.tiles[0], { minZoom: tj.minzoom, maxZoom: tj.maxzoom, attribution: tj.attribution }).addTo(map)).catch(console.error). It costs one extra request, and the attribution comes with it.

Tilesets

All six cover Florida and run from zoom 4 to 21. GET /tiles lists exactly the tilesets your own key can fetch, each with its tile template and TileJSON path.

Aerials — Latest aerials-latest

Imagery: most recent available per county · FDOT / FDOR / Florida counties. TileJSON: https://www.mapwise.com/api_v2/tiles/aerials-latest/tiles.json

Aerials — NAIP Latest aerials-naip-latest

NAIP latest · USDA NAIP. TileJSON: https://www.mapwise.com/api_v2/tiles/aerials-naip-latest/tiles.json

Aerials — 2023 CIR aerials-naip-2023-cir

NAIP 2023 CIR · USDA NAIP, colour-infrared. TileJSON: https://www.mapwise.com/api_v2/tiles/aerials-naip-2023-cir/tiles.json

Aerials — 2004 DOQ aerials-doq2004

2004 digital orthophoto quads · FDOT / FDOR. TileJSON: https://www.mapwise.com/api_v2/tiles/aerials-doq2004/tiles.json

Aerials — 1999 CIR aerials-doq1999

1999 digital orthophoto quads, colour-infrared · USGS. TileJSON: https://www.mapwise.com/api_v2/tiles/aerials-doq1999/tiles.json

Aerials — 1995 CIR aerials-doq1995

1995 digital orthophoto quads, colour-infrared · USGS. TileJSON: https://www.mapwise.com/api_v2/tiles/aerials-doq1995/tiles.json

Billing and caching

  • $0.50 / 1,000 tiles, flat. Every tile request that reaches the API counts. Usage is summed per calendar month and rounded up to the next 1,000 tiles when the month closes, so the smallest monthly tile charge is one block at that rate. There is no free allowance at launch.
  • A tile served from cache while it is still fresh never reaches the API, and is never billed. That is the one thing that lowers your bill, so let tiles cache: do not disable the browser cache, do not append cache-busting parameters, and do not serve them through a proxy, CDN, or other shared cache.
  • A revalidation is a billed request. Every tile carries an ETag, and a Last-Modified where the imagery backend publishes one. When a cached tile's lifetime has elapsed your client asks whether it is still current, and that question reaches the API, so it counts, even when the answer is 304 Not Modified and no image comes back. Revalidation saves you bandwidth and time, not money. The only way to spend less is to not send the request.
  • Cache lifetimes: 24 hours on the rolling imagery (aerials-latest, aerials-naip-latest) and 30 days, marked immutable, on the dated vintages. A dated vintage never changes, so a well-behaved client should serve it from cache until it expires and not revalidate it at all.
  • A blank tile outside the imagery footprint bills like any other, as does a tile at a zoom with no imagery. bounds, minzoom and maxzoom let your client skip those before it asks; libraries that read the TileJSON do it automatically.
  • What does not bill: a request refused before any imagery is fetched. That covers 403 (a site the key does not list, or the wrong key class), 404 (unknown tileset), 402 (monthly spend cap reached) and 502 (the imagery backend failed).
  • Tile requests are capped. The tile image route accepts 60 tiles per second, 600 tiles per minute, 100,000 tiles per day. Past a cap you get a 429 with a Retry-After header; see Rate Limiting.
  • Your monthly spend cap is the ceiling. Per-tile spend is hard to forecast from a map's behaviour, so the cap, not a rate sheet, is what makes it predictable: it is $100 by default and you set it under My Account → MapWise API. Tile requests are declined once it is reached.

List the tilesets you can draw

GET /tiles

The aerial tileset catalog, scoped to the credential you send: it lists only what your own key can fetch, so everything in it is a tileset you can request. url_template and tilejson are absolute paths that already carry the /api_v2 prefix, so join them to the scheme and host of the server URL above, not to its full URL. Or skip the arithmetic and take the ready-made absolute template from the tileset's TileJSON. Because the answer depends on your credential it is served Cache-Control: private, no-store; do not hold it in a shared cache.

Returns the tilesets this credential can fetch. Anything else is a refusal (see Error Responses).

Response 200

successboolean
statusinteger
timestampstring
dataobject
tilesetsarray of object
namestringThe identifier used in every tile URL.
display_namestring
vintagestringWhich imagery this is, in words.
tierstringThe access tier this tileset sits in.
url_templatestringThe XYZ tile path. It already carries the /api_v2 prefix, so join it to the host alone and add your key as apiKey, or take the ready-made absolute template from the TileJSON instead.
tilejsonstring nullablePath to this tileset's TileJSON, or null when it publishes none.

Errors

StatusMeaning
401Missing or invalid key.
403Told apart by errormsg: Tiles require a publishable key (code KEY_CLASS_MISMATCH): a secret key was sent; use a publishable one. Origin not permitted for this API key: the site is not on the key, or the request carried no Origin and no Referer; add the site to the key. Browser origin is not permitted (code ORIGIN_NOT_ALLOWED): the request came from a browser page and carried no key at all; add apiKey to the URL.

TileJSON for one tileset

GET /tiles/{tileset}/tiles.json

TileJSON 3.0.0 (https://github.com/mapbox/tilejson-spec/tree/master/3.0.0) for one tileset: the tile template with your key already in it, the coverage bounds, the minzoom/maxzoom that actually have imagery, and the attribution you must display. MapLibre and OpenLayers read this document directly; Leaflet has no TileJSON loader, so pass it the tiles[0] template instead. It echoes the key you authenticated with, so it is served Cache-Control: private, no-store.

Returns the tileset's TileJSON document. Anything else is a refusal (see Error Responses).

Query parameters

ParameterTypeDescription
tileset requiredstringThe tileset's name, as GET /tiles lists it.

Response 200

tilejsonstring
namestring
descriptionstring
attributionstringDisplay this, and do not let your interface cover it. It credits the imagery source and the tile service in one string. MapLibre, Leaflet and OpenLayers render it as HTML in their attribution control; the TileJSON spec lets a client treat the field as plain text instead, so handle the markup if you render it yourself.
schemestring
tilesarray of stringThe absolute tile templates, with your key already embedded. That is what lets a plain raster source work with no request interception.
minzoominteger
maxzoominteger
boundsarray of numberCoverage as [west, south, east, north] in EPSG:4326.
centerarray of numberA suggested [longitude, latitude, zoom].

Errors

StatusMeaning
401Missing or invalid key.
403Told apart by errormsg: Tiles require a publishable key (code KEY_CLASS_MISMATCH): a secret key was sent; use a publishable one. Origin not permitted for this API key: the site is not on the key, or the request carried no Origin and no Referer; add the site to the key. Browser origin is not permitted (code ORIGIN_NOT_ALLOWED): the request came from a browser page and carried no key at all; add apiKey to the URL. Tileset not available via API key: this key cannot reach that tileset; check the catalog for what it can.
404Unknown tileset, or one that publishes no TileJSON.

Fetch one map tile

GET /tiles/{tileset}/{z}/{x}/{y}.jpg

One 256 px JPEG tile, in the usual XYZ scheme. Your map library requests these from the template in the TileJSON. You should not need to build the URL yourself. Billed per tile. A tile served from a cache while it is still fresh never reaches the API and is never billed, but a revalidation does reach it: send If-None-Match with the ETag (or If-Modified-Since with the Last-Modified) and an unchanged tile answers 304 Not Modified with no body. That saves the bytes and the latency, not the charge. A blank tile outside the imagery footprint is billed like any other, so let bounds, minzoom and maxzoom keep your client from asking for it. The .jpg extension is part of the last path segment: request {y}.jpg, not {y}.

Returns one 256 px JPEG tile. Anything else is a refusal (see Error Responses).

Query parameters

ParameterTypeDescription
tileset requiredstringThe tileset's name, as GET /tiles lists it.
z requiredintegerZoom level. The API accepts 0 to 24, but imagery exists only between the tileset's TileJSON minzoom and maxzoom; a tile outside that range is blank and still billed.
x requiredintegerTile column.
y requiredintegerTile row. The path segment carries the extension too, as {y}.jpg.

Errors

StatusMeaning
304Not Modified: the tile still matches the If-None-Match or If-Modified-Since you sent. No body. The validators and Cache-Control are repeated so you can refresh your stored copy. Billed like a 200.
400Bad tile coordinates, or an extension this tileset does not serve. Use the one in the TileJSON's tiles[] template.
401Missing or invalid key.
402Your monthly spend cap is reached. The cap is checked before the tile is fetched, so nothing is billed and waiting does not help until the period resets or the cap is raised.
403Told apart by errormsg: Tiles require a publishable key (code KEY_CLASS_MISMATCH): a secret key was sent; use a publishable one. Origin not permitted for this API key: the site is not on the key, or the request carried no Origin and no Referer; add the site to the key. Browser origin is not permitted (code ORIGIN_NOT_ALLOWED): the request came from a browser page and carried no key at all; add apiKey to the URL. Tileset not available via API key: this key cannot reach that tileset; check the catalog for what it can. None of these are billed.
404Unknown tileset. Not billed.
429Too many requests. Read Retry-After and back off; a map library firing a whole viewport of tiles at once is the usual cause. Not billed. Each key also has its own limits, answered in the standard envelope and told apart by errormsg: Too many requests means more than 600 a minute, and Daily request limit exceeded means the cap of 100,000 tiles a day is reached, which only the next day clears.
500Unexpected server failure while rendering the tile.
502The imagery server did not answer. Nothing is billed (the meter records a tile only once one has been fetched), so a retry costs you nothing.

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.

StatusDescriptionCommon cause
400Bad RequestMissing searchCounty, lat_lon, or bbox
401UnauthorizedMissing or invalid API key, one that was revoked, or one whose subscription has lapsed. See Authentication for the headers that carry it.
404Not FoundNo 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.
403ForbiddenA 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.
403ForbiddenOrigin 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. A server-side call cannot use a publishable key.
404Not FoundUnknown tileset: the tileset name is wrong, or your plan does not include it.
429Too Many RequestsRate limit (see Rate Limiting) 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
500Internal Server ErrorUnexpected server failure

Refusals when creating a key

These come from My Account rather than from a data request, and the dashboard shows their message. Each is a 403, and all but the first carry the code shown:

CodeMeaning
no code“A billing or admin role is required for API key management.” Your user cannot manage keys; ask the account’s Super Admin or Billing user, or have them change your role.
STRIPE_CUSTOMER_REQUIREDThe account is not on our current payment system, as with some older or offline subscriptions. Contact us to move it.
SUBSCRIPTION_REQUIREDThe account is on a free trial or on MapWise Plus. API keys need a paid MapWise Pro subscription.
PAYMENT_METHOD_REQUIREDNo payment method is on file. Add one from the Billing tab in My Account.
LIMIT_REACHEDYou already hold the most keys of that kind allowed. Revoke one you no longer use; see Managing secret keys.

Best Practices

  • Validate all user-supplied input before sending requests.
  • Check meta.record_count to determine if any results were returned.
  • Handle non-200 HTTP status codes gracefully in your application.
  • Implement exponential backoff when you receive a 429 response.
  • All error responses include success: false and an errormsg field.
  • Give each integration its own secret key, and rotate it with the rotation steps, which overlap the old and new keys so nothing fails in between.

Contact & Support

Questions about the API, higher rate limits, or enterprise pricing? Contact us.