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://www.mapwise.com/api_v2
- 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.
- Make a first request. From a terminal or your server, run a small search:
Replacecurl "https://www.mapwise.com/api_v2/parcels?searchCounty=ALACHUA&limit=1" \ -H "X-API-Key: YOUR_API_KEY"YOUR_API_KEYwith 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. - Check the response. Success is HTTP
200with"success": true, the matching parcels indata, and how many came back inmeta.record_count. The full response is shown under Search parcels. A401means the key was missing or wrong; every other failure is listed in Error Responses. - 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.
| Key | What it can reach | Where 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:
- Create a new secret key and store it wherever your integration reads its secrets.
- Switch your integration to the new key, and confirm its requests succeed.
- 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_KEYAuthorization: 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:
| Product | Rate | What it covers |
|---|---|---|
| Parcels | $0.06 / record | Parcel lookups, base data |
| Tiles | $0.50 / 1,000 tiles | Tile 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/testdo 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.
| Endpoint | Bucket | Sustained | Burst | Per minute | Per day |
|---|---|---|---|---|---|
GET /parcels | account | 10/s | 600 | - | - |
GET /parcels/test | endpoint | 5/s | - | - | - |
GET /tiles | account | 10/s | 600 | - | - |
GET /tiles/{tileset}/tiles.json | endpoint | 60/s | - | - | - |
GET /tiles/{tileset}/{z}/{x}/{y}.jpg | endpoint | 60/s | - | 600/min | 100,000/day |
Every response tells you where you stand, so a client can pace itself rather than guess:
| Header | Meaning |
|---|---|
X-RateLimit-Bucket-Capacity | Your burst allowance: the most requests your bucket holds. |
X-RateLimit-Bucket-Refill-Rate | Requests per second added back to your bucket. |
X-RateLimit-Bucket-Remaining | Requests left in your bucket now. |
X-RateLimit-Endpoint-Limit | Requests this endpoint allows per window, counted per account (or per IP address without a key). |
X-RateLimit-Endpoint-Remaining | Requests left in the current window. |
X-RateLimit-Endpoint-Reset | When the current window ends, as a Unix timestamp in seconds. |
X-RateLimit-Global-Limit | Requests per minute the service accepts across all callers. A shared safeguard, not your own allowance. |
X-RateLimit-Global-Remaining | Requests left in the shared minute. |
X-RateLimit-Global-Reset | When 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
/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. Reduce the page size and paginate with offset. |
start Deprecated | integer | Row offset, and an alias of offset. Send both and start wins. Deprecated: use offset instead. |
offset | integer | Row offset for pagination. Ignored when start is also sent. |
order | string | Sort direction. Case-insensitive. enum: ASC · DESC |
dir Deprecated | string | Sort direction. Deprecated: use order instead. Case-insensitive. enum: ASC · DESC |
sortBy | string | Field to sort by. Case-insensitive. enum: owner · county · pin · acres · sale_date |
sort Deprecated | string | Field to sort by. Deprecated: use sortBy instead. Case-insensitive. enum: owner · county · pin · acres · sale_date |
bufferPoint | boolean | Treat lat_lon as the center of a buffer. Also accepts 1/0 and yes/no. |
bufferMeters | integer | Used with bufferPoint. Buffer radius in METRES (1-50000). 1 mile ~ 1609, 1 foot ~ 0.3. |
includeGeometry | boolean | Include parcel geometry in the response. Also accepts 1/0 and yes/no. |
includeLegal | boolean | Include the legal description. Also accepts 1/0 and yes/no. |
geometry Deprecated | boolean | Older spelling of includeGeometry. Deprecated: use includeGeometry instead. Also accepts 1/0 and yes/no. |
searchCounty | string | County 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 | string | Several 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 | 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). |
searchCity | string | Site city. |
searchCityOwner | string | Owner-address city. |
searchStateOwner | string | Owner-address state. |
searchZipcodeOwner | string | Owner-address ZIP (5-digit or ZIP+4). |
searchAddress | string | Site street address (partial match). |
searchNumber | string | Site street number. |
searchStreet | string | Site street name. |
searchUnit | string | Site unit. |
searchAcresMin | number | Smallest parcel size, in acres. |
searchAcresMax | number | Largest parcel size, in acres. |
searchAcres1 Deprecated | number | Older spelling of searchAcresMin. Deprecated: use searchAcresMin instead. |
searchAcres2 Deprecated | number | Older spelling of searchAcresMax. Deprecated: use searchAcresMax instead. |
saleAmountMin | number | Lowest sale price, in dollars. |
saleAmountMax | number | Highest sale price, in dollars. |
saleDateMin | string <date> | Earliest sale date (YYYY-MM-DD). |
saleDateMax | string <date> | Latest sale date (YYYY-MM-DD). |
lat_lon | string | Point "lat,lon" for point-in-parcel search. |
bbox | string | Bounding box xmin,ymin,xmax,ymax (EPSG:4326). |
searchZipcode | string | Site ZIP (5-digit or ZIP+4). |
searchSqftHtdMin | number | Smallest heated area, in square feet. |
searchSqftHtdMax | number | Largest heated area, in square feet. |
searchSqftTotMin | number | Smallest total building area, in square feet. |
searchSqftTotMax | number | Largest total building area, in square feet. |
searchSqftAdjMin | number | Smallest adjusted building area, in square feet. |
searchSqftAdjMax | number | Largest adjusted building area, in square feet. |
searchYearBuiltMin | integer | Earliest actual year built. |
searchYearBuiltMax | integer | Latest actual year built. |
searchYearBuiltEffMin | integer | Earliest effective year built. |
searchYearBuiltEffMax | integer | Latest effective year built. |
searchMarketValueMin | number | Lowest total market value, in dollars. |
searchMarketValueMax | number | Highest total market value, in dollars. |
searchLandUse | string | DOR land-use code(s), comma-separated. |
searchLandUse4 | string | 4-digit land-use code(s), comma-separated. |
searchSubdivision | string | Subdivision name (partial match). |
searchLegal | string | Legal description (partial match). Format: ^[\p{L}\p{N}\s\-_\.,#*@&\/:'"()]+$, Unicode. |
searchBook | string | Official records book of the sale. |
searchPage | string | Official records page of the sale. |
searchDocnum | string | Official records document number of the sale. |
searchGeometry | string | WKT geometry for spatial search (GeoJSON via POST body). |
Response 200
successbooleanstatusintegertimestampstringWhen the response was generated, server time.dataarray of objectmetaobjectogc_fidintegercountystringextracted_atstringWhen this parcel was last extracted from the source.countintegerpa_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.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 nullablequalificationobject 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 objectdatestring nullableamountinteger nullabletypestring nullablequalstring nullablevacstring nullablebookstring nullablepagestring nullabledocnumstring nullablegrantorstring nullablequalificationobject 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 nullablelegal3string nullablelegal4string nullablelegal5string nullablelegal6string nullablemetaobjectrecord_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
| 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. |
Test parcel search (no API key required)
/parcels/testTry 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
| Parameter | Type | Description |
|---|---|---|
limit | string | Page size. |
start Deprecated | string | Row offset, and an alias of offset. Send both and start wins. Deprecated: use offset instead. |
offset | string | Row offset for pagination. Ignored when start is also sent. |
searchCounty required | string | County name. Only ALACHUA is accepted. Case-insensitive. Spaces, underscores and hyphens are interchangeable. enum: ALACHUA |
searchPin | string | Parcel identification number. |
searchParcelId | string | Parcel ID. |
Response 200
successbooleanstatusintegertimestampstringWhen the response was generated, server time.dataarray 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_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).sqftobjectheatedstring 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).bathsobjectfullstring 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).valuationobjectmarketobjectbuildingstring 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 nullablequalificationobject nullablecodestring 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 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 nullablequalificationobject nullablecodestring 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).metaobjectrecord_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_modebooleandata_scopeobjectcountystringErrors
| Status | Meaning |
|---|---|
400 | Invalid parameter. |
403 | Owner-name search refused (searchOwner, documented on /parcels): an anonymous caller cannot search by owner (data.error_code PLUS_REQUIRED). |
429 | Too many requests. This endpoint is limited more tightly than the keyed ones; back off and retry. |
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.
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>const map = L.map('map').setView([28.54, -81.38], 12);
// Leaflet has no TileJSON loader, so the tile template goes in directly.
// minZoom/maxZoom and bounds keep it from asking for tiles that do not
// exist. Every request is billed, including a blank one.
L.tileLayer('https://www.mapwise.com/api_v2/tiles/aerials-latest/{z}/{x}/{y}.jpg?apiKey=mw_pk_YOUR_PUBLISHABLE_KEY', {
tileSize: 256,
minZoom: 4,
maxZoom: 21,
bounds: L.latLngBounds([24.5, -87.6], [31.2, -80.0]),
attribution: '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>', // required
}).addTo(map);const layer = new ol.layer.Tile({
source: new ol.source.TileJSON({
url: 'https://www.mapwise.com/api_v2/tiles/aerials-latest/tiles.json?apiKey=mw_pk_YOUR_PUBLISHABLE_KEY',
crossOrigin: 'anonymous', // tiles and tiles.json both send Access-Control-Allow-Origin: *
}),
});
new ol.Map({
target: 'map',
layers: [layer],
view: new ol.View({ center: ol.proj.fromLonLat([-81.38, 28.54]), zoom: 12 }),
});
// Attribution is required. OpenLayers takes it from the TileJSON:
// 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 aLast-Modifiedwhere 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 is304 Not Modifiedand 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, markedimmutable, 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,minzoomandmaxzoomlet 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) and502(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
429with aRetry-Afterheader; 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
/tilesThe 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
successbooleanstatusintegertimestampstringdataobjecttilesetsarray of objectnamestringThe identifier used in every tile URL.display_namestringvintagestringWhich 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
| Status | Meaning |
|---|---|
401 | Missing or invalid key. |
403 | Told 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
/tiles/{tileset}/tiles.jsonTileJSON 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
| Parameter | Type | Description |
|---|---|---|
tileset required | string | The tileset's name, as GET /tiles lists it. |
Response 200
tilejsonstringnamestringdescriptionstringattributionstringDisplay 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.schemestringtilesarray of stringThe absolute tile templates, with your key already embedded. That is what lets a plain raster source work with no request interception.minzoomintegermaxzoomintegerboundsarray of numberCoverage as [west, south, east, north] in EPSG:4326.centerarray of numberA suggested [longitude, latitude, zoom].Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid key. |
403 | Told 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. |
404 | Unknown tileset, or one that publishes no TileJSON. |
Fetch one map tile
/tiles/{tileset}/{z}/{x}/{y}.jpgOne 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
| Parameter | Type | Description |
|---|---|---|
tileset required | string | The tileset's name, as GET /tiles lists it. |
z required | integer | Zoom 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 required | integer | Tile column. |
y required | integer | Tile row. The path segment carries the extension too, as {y}.jpg. |
Errors
| Status | Meaning |
|---|---|
304 | Not 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. |
400 | Bad tile coordinates, or an extension this tileset does not serve. Use the one in the TileJSON's tiles[] template. |
401 | Missing or invalid key. |
402 | Your 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. |
403 | Told 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. |
404 | Unknown tileset. Not billed. |
429 | Too 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. |
500 | Unexpected server failure while rendering the tile. |
502 | The 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.
| Status | Description | Common cause |
|---|---|---|
400 | Bad Request | Missing searchCounty, lat_lon, or bbox |
401 | Unauthorized | Missing or invalid API key, one that was revoked, or one whose subscription has lapsed. See Authentication for the headers that carry it. |
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. A server-side call cannot use a publishable key. |
404 | Not Found | Unknown tileset: the tileset name is wrong, or your plan does not include it. |
429 | Too Many Requests | Rate 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 |
500 | Internal Server Error | Unexpected 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:
| Code | Meaning |
|---|---|
| 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_REQUIRED | The account is not on our current payment system, as with some older or offline subscriptions. Contact us to move it. |
SUBSCRIPTION_REQUIRED | The account is on a free trial or on MapWise Plus. API keys need a paid MapWise Pro subscription. |
PAYMENT_METHOD_REQUIRED | No payment method is on file. Add one from the Billing tab in My Account. |
LIMIT_REACHED | You 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_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. - 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.