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 right away, because 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 and pricing. To see the response format first, send the same query to/parcels/test. It needs no key, costs nothing and answers with masked Alachua County records. - Check the response. Success is HTTP
200with"success": true. The matching parcels are indata, andmeta.record_countsays how many came back. See Search parcels for the full response. A401means the key was missing or wrong. See Error responses for every other failure. - Where to go next. See Parcels for every search parameter. See Rate limiting and Monthly spend caps for the limits on your key. Read Rotate 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 only the sites you use. |
Both keys bill to your account. MapWise Pro includes API access and $20 of API usage each month. Everything above that, read or drawn by either key, is charged at the rates below. The two keys differ in reach, not cost. A secret key can read every product you pay for, from anywhere. A publishable key can only draw tiles, and only from the sites you listed. Keep that list short, and watch your usage on Usage & spend.
Manage 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, these older keys show their last four characters with no prefix.
The oldest were made before MapWise recorded any part of a key for display, and are listed as
Legacy key.
Who can create keys. Keys need a paid MapWise Pro subscription with a payment method on file. On a free trial, you can create a key once the trial converts to paid. Creating, renaming and revoking keys needs the Super Admin or Billing role. If the API refuses a request, 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 the key again. After that, the list shows the key’s label
and, for an
mw_sk_key, its last four characters. If you lose a key, create a new one and revoke the old one. - 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.
You can rename a label; the key itself does not change. At the limit, MapWise refuses a new key with
LIMIT_REACHED. Revoke one you no longer use. - Revoking. Revoke a key from the same list. It stops working on the next request,
which gets a
401. You cannot restore it. You can revoke your last key and create another later. - Security email. When a key, secret or publishable, is created or revoked, MapWise sends a security email. It goes 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. You cannot turn it off. Delivery is best-effort. If the email cannot be sent, the key action still succeeds, so treat the list in My Account as the record of your keys. If you did not expect an email, revoke the key and contact us.
Rotate a key
To rotate a key, run the old and new keys side by side for a moment, so no request fails in between. There is no rotate button.
- Create a new secret key, and store it where your integration reads its secrets.
- Switch your integration to the new key, and confirm that its requests succeed.
- Revoke the old key in My Account.
Rotate a key when someone who had it leaves, when it may have been exposed, or on your own security policy’s schedule. If a key has leaked, revoke it before you create its replacement. Do not wait for the switch.
Send a key
Send a secret key on every data request to https://www.mapwise.com/api_v2, in either header:
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. The user of a device can read anything shipped to it. A key committed to a repository stays in its history after you delete the file. Do not put the key in a URL either, because URLs end up in logs.
If you send the wrong class of key, MapWise refuses the request rather than downgrading it. 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 and pricing
API access is included with MapWise Pro, and so is $20 of API usage each month. There is no separate API plan. Above that, you pay for what you use, billed monthly:
| Product | Rate | What it covers |
|---|---|---|
| Parcels | $0.06 / record | Parcel lookups, base data |
| Reports | $1.00 / report | PDF report generation |
| Tiles | $0.50 / 1,000 tiles | Tile blocks (1,000 tiles per unit) |
These are standard rates. My Account shows your account’s own rates, including any negotiated price, under Usage & spend. For volume pricing, contact us.
- County coverage: all 67 Florida counties with complete sales history and parcel 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 has a monthly spend cap, $0 by default, so paid usage is off until you raise it. The cap counts usage above the $20 included with Pro, so with the default cap, billable requests stop at $20 of usage. To change it, open
My Account > MapWise API > Monthly Spend Cap. You need the Super Admin or Billing role.
The cap can go up to the account maximum of $1,000/month. For higher limits,
contact us about enterprise pricing. Once you reach the cap, MapWise declines further
billable requests with 429 until usage resets on the 1st of the month (00:00 UTC).
MapWise emails you at 80% and 100% of your included usage and of the cap. The 80% emails can be turned off, and a 50% email turned on, in your alert settings.
Security note: a leaked key cannot raise its own cap. Changing the cap needs a signed-in 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. Its body keeps the top-level
error field, spend_cap_exceeded, which is still the way to tell it apart from a
rate limit. It also carries the members every other failure carries: success,
status, errormsg, type, title and
detail, plus a top-level code (spend_cap_exceeded) and
resets_at, the instant the period resets:
{
"success": false,
"status": 429,
"errormsg": "Your spend cap for this billing period is reached. It resets at 2026-10-01T00:00:00Z.",
"type": "https://www.mapwise.com/api_v2/problems/spend_cap_exceeded",
"title": "Spend cap reached",
"detail": "Your spend cap for this billing period is reached. It resets at 2026-10-01T00:00:00Z.",
"code": "spend_cap_exceeded",
"resets_at": "2026-10-01T00:00:00Z",
"error": "spend_cap_exceeded",
"endpoint": "parcels",
"current_spend_cents": 5000,
"cap_cents": 5000,
"reset_at": "2026-10-01T00:00:00Z",
"estimated_call_cost_cents": 6,
"request_id": "01a0ba0a-e8f4-7fff-853c-bbce832d6c46"
}
Its Retry-After is set to the period reset, which may be days away. Backing off will not help
before reset_at, so do not treat it like a rate limit. Raise the cap in My Account, or wait for
the reset. Compare current_spend_cents with cap_cents to decide which.
Rate limiting and 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 that, 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 | - | - | - |
POST /reports | endpoint | 20/s over 60 s | - | - | - |
GET /reports/allowance | account | 10/s | 600 | - | - |
GET /reports/{id} | account | 10/s | 600 | - | - |
GET /reports/{id}/pdf | account | 10/s | 600 | - | - |
GET /reports/{id}/preview | account | 10/s | 600 | - | - |
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. |
A request over the limit gets a 429 with a Retry-After header that says when to
try again. A 429 can also mean you reached your monthly spend cap, not your rate
limit. The body’s error field says which, and its top-level code says the same
(spend_cap_exceeded or rate_limited). A value of
spend_cap_exceeded means waiting will not help until the period resets. Either way, back off
exponentially. Do not retry in a tight loop.
These limits are defaults, not ceilings. For more throughput, contact us.
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
Parcel search and retrieval.
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 | Geometry for spatial search, as WKT or GeoJSON geometry text. |
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.identifiersobjectpinstringupinstring nullableThe universal PIN: the county's 5-digit FIPS, a hyphen, then pin exactly as the county writes it, for example 12083-0757-000-001. Null when the row has no pin or its county has no Florida FIPS. The Selection routes name parcels by it.pin_cleanstringpin2string nullablepin2_cleanstring nullablealtkeystring nullableprop_detailsstring nullableownerobjectprimary_namestringsecondary_namestring nullabletertiary_namestring nullableaddress_line1stringaddress_line2string nullableaddress_line3string nullablecitystringstatestringzipcodestringabsentstringsiteobjectaddressstring nullablecitystring nullablezipcodestring nullablesubdivisionstring | object nullableabsentstringsubdivision_commonstring | object nullableabsentstringsubdivision_idstring | object nullableabsentstringcondostring nullablelandobjectacres_deednumber nullableacres_gisnumber nullablezoningstring nullableland_useobjectlusestring nullableluse_descstring nullablelusedorstring nullablelusedor_descstring nullableabsentstringbuildingobjectnum_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 nullableThe county's sale qualification as the MapWise pipeline standardizes it. In a county the current standardizer has processed, the verdict: Q (qualified), U (unqualified) or N (the appraiser gave no verdict). In a county not yet reprocessed, the Florida DOR two-digit code.qual_codestring nullableThe Florida DOR two-digit sale-qualification code beside a Q/U/N verdict in qual, where the county's value reaches one. Null for a verdict-only county, a pre-2009 sale, or a county whose qual still holds the DOR code.vacstring nullablebookstring nullablepagestring nullabledocnumstring nullablegrantorstring nullablequalificationobject nullableMapWise's reading of qual and qual_code against the Florida DOR sale-qualification codes. A Q/U/N verdict sets status; a DOR code beside it, on a sale dated 2009-01-01 or later, supplies code and reason. 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/N where there is only a verdict.schemestringThe code system code belongs to: fl_dor (Florida DOR sale-qualification codes) or county (a Q/U/N verdict with no DOR code). Extensible: treat an unknown value as unknown.statusstringOne of: qualified (DOR 01–06, Q), disqualified (11–21, 30–43, U), pending (98, 99, N). Where qual is a verdict it sets the status, even when the DOR code beside it reads otherwise. Extensible: treat an unknown value as unknown.reasonstring nullableShort plain-language meaning of the DOR code; null for a verdict with no DOR code.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. To display an instrument, show type_label, else type unless it is all digits: some counties store a transaction or qualification code there, which is not an instrument.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 nullableThe county's sale qualification as the MapWise pipeline standardizes it. In a county the current standardizer has processed, the verdict: Q (qualified), U (unqualified) or N (the appraiser gave no verdict). In a county not yet reprocessed, the Florida DOR two-digit code.qual_codestring nullableThe Florida DOR two-digit sale-qualification code beside a Q/U/N verdict in qual, where the county's value reaches one. Null for a verdict-only county, a pre-2009 sale, or a county whose qual still holds the DOR code.vacstring nullablebookstring nullablepagestring nullabledocnumstring nullablegrantorstring nullablequalificationobject nullableMapWise's reading of qual and qual_code against the Florida DOR sale-qualification codes. A Q/U/N verdict sets status; a DOR code beside it, on a sale dated 2009-01-01 or later, supplies code and reason. 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/N where there is only a verdict.schemestringThe code system code belongs to: fl_dor (Florida DOR sale-qualification codes) or county (a Q/U/N verdict with no DOR code). Extensible: treat an unknown value as unknown.statusstringOne of: qualified (DOR 01–06, Q), disqualified (11–21, 30–43, U), pending (98, 99, N). Where qual is a verdict it sets the status, even when the DOR code beside it reads otherwise. Extensible: treat an unknown value as unknown.reasonstring nullableShort plain-language meaning of the DOR code; null for a verdict with no DOR code.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. To display an instrument, show type_label, else type unless it is all digits: some counties store a transaction or qualification code there, which is not an instrument.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.coverageobjectUnstable, and sent only where non-Florida parcels are enabled. Present on a non-aggregated search with at least one overlay include: for parcels and each requested overlay (keyed like meta.datasets), whether the states on this page are covered by that dataset.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.redactedbooleanmessagestringidentifiersobjectpinstringupinstring nullableThe universal PIN: the county's 5-digit FIPS, a hyphen, then pin exactly as the county writes it, for example 12083-0757-000-001. Null when the row has no pin or its county has no Florida FIPS. The Selection routes name parcels by it.pin_cleanstringpin2string nullablepin2_cleanstring nullablealtkeystring nullableprop_detailsstring nullableownerobjectredactedbooleanmessagestringabsentstringsiteobjectaddressstring nullablecitystring nullablezipcodestring nullablesubdivisionstring | object nullableabsentstringsubdivision_commonstring | object nullableabsentstringsubdivision_idstring | object nullableabsentstringcondostring 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_useobjectredactedbooleanmessagestringabsentstringbuildingobjectnum_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).qual_codestring 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).schemestring 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).qual_codestring 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).schemestring 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.coverageobjectUnstable, and sent only where non-Florida parcels are enabled. Present on a non-aggregated search with at least one overlay include: for parcels and each requested overlay (keyed like meta.datasets), whether the states on this page are covered by that dataset.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.
For complete access, use the full /parcels endpoint with your API key.
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 — NAIP Latest CIR aerials-naip-latest-cir
NAIP latest color infrared · USDA NAIP. TileJSON: https://www.mapwise.com/api_v2/tiles/aerials-naip-latest-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,aerials-naip-latest-cir) 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),429(monthly spend cap reached, codespend_cap_exceeded, or a rate limit) 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 $0 by default, so paid tile requests are off until 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 data, 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).
Path 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. For an API key the key is already embedded, which 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].vector_layersarray of objectVector tilesets only (TileJSON 3.0.0 requires it for them; raster tilesets omit it). One entry per source layer inside each tile: id is the source-layer name to style against, fields maps each attribute to a description that names its value type as encoded in the tile. No tileset an API key can reach is a vector tileset yet, so an API key never sees this field today.idstringdescriptionstringminzoomintegermaxzoomintegerfieldsobjectErrors
| 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).
Path 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. |
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. Code spend_cap_exceeded means your monthly spend cap is reached. Its body is the SpendCapExceeded shape every capped route sends, with the billing state in the body and the X-Billing-Period-* headers. It is checked before the tile is fetched, so nothing is billed. Only the period reset at resets_at, or a higher cap, clears it. |
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. |
Reports
Property and flood reports for a parcel, as a PDF: create one, then fetch it, its PDF or its page-1 preview.
New to reports? Read the step-by-step guide. Not writing code? Make the same reports in MapWise Explorer.
Create a report
/reportsGenerates a PDF report for one Florida parcel and stores it: a property report (the parcel record, sales, building, values, legal description and soils) or a flood report (the FEMA flood zones over the parcel). Name the parcel by subject.pin and subject.metadata.county: a county sent beside pin is not read. A flood report also takes a point or a polygon. The answer is a reference to the report: its id, links to its PDF and page-1 preview, and a download link that works without a key for 15 minutes. Each report is billed once, meta.record_count, checked against your spend cap first; fetching it again is free. A request that is refused stores and bills nothing. GET /reports/allowance lists the report types and sections your key can create here.
Returns 201: the report is stored. Anything else is a refusal (see Error responses).
Header parameters
| Parameter | Type | Description |
|---|---|---|
Idempotency-Key | string | Makes a retry safe: the same key and body replay the first answer with Idempotent-Replayed: true, start no second job and bill nothing again. Printable ASCII. Any other key is a 400; the same key with another body is a 422; a key whose first request is still running is a 409 with Retry-After: 2; a key older than 24 hours is a 409 idempotency_key_reused. |
Request body application/json required
The report type and the parcel it is about, and an optional title.
typestring requiredThe report to make. The types listed here are the ones this host offers; GET /reports/allowance says which your key can create.One of property, flood.subjectobject requiredWhat the report is about. A property report takes a parcel; a flood report also takes a point or a polygon.kindstring requiredOne of parcel, point, polygon.pinstringThe parcel id, as /parcels returns it. Required for a parcel.latnumberRequired for a point.lngnumberRequired for a point.geometryobjectA GeoJSON Polygon in longitude and latitude. Required for a polygon.typestring requiredOnly Polygon.coordinatesarray of array of array of number requiredmetadataobjectSend the parcel's county: parcel ids repeat across counties, and a pin found in two is a 422.countystringaddressstringUsed for the report's subject when the parcel record has no site address.titlestringThe report's name. Left out, it is the parcel's address and the type. Each of your reports has its own title: a title you already use is a 409.Response 201
successbooleanstatusintegertimestampstringmetaobjectOn a create only, and absent on an idempotent replay.record_countinteger1: the report this create billed.dataobjectWhich report this is and where to get it. The report's content is in the PDF.idstringrpt_ and a UUID.typestringtitlestringstatusstringredactedbooleantrue for a report made without a paid plan. It has no PDF or preview for its owner.subjectobjectkindstringpinstringcountystringFrom the report's parcel record, else the metadata.county you sent.site_addressstringFrom the report's parcel record, else the metadata.address you sent.created_atstring <date-time>generated_atstring <date-time>download_urlstringDownloads the PDF without a key, for a person to open. A new link on every response; left out when one could not be made.expires_atstring <date-time>When download_url stops working, 15 minutes after this response.preview_urlstring nullablePage 1 as a thumbnail, GET /reports/{id}/preview?size=thumb, once it is stored; null until then.pdf_urlstring nullableGET /reports/{id}/pdf, with your key. It does not expire. Null for a redacted report.Errors
| Status | Meaning |
|---|---|
400 | No type, or one that is not a report type; a subject that breaks a rule (no kind, a parcel with no pin, a point outside -90 to 90 and -180 to 180, a polygon that is not a GeoJSON Polygon); or the Idempotency-Key is not a usable key. |
401 | No API key, an invalid one, or an anonymous token. |
403 | A report type this key cannot create on this host (data.error_code REPORT_TYPE_UNAVAILABLE, data.reason plan or not_released), or a publishable key, where this route takes a secret one (data.code KEY_CLASS_MISMATCH). |
404 | No parcel matches the subject (data.error_code SUBJECT_NOT_FOUND). |
409 | You already have a report with this title (data.suggestedTitle is a free one); the Idempotency-Key belongs to a request still in progress (Retry-After: 2); or it belongs to an older report (data.error_code idempotency_key_reused). |
422 | The subject matches more than one parcel (data.error_code SUBJECT_AMBIGUOUS): send subject.metadata.county. Also the same Idempotency-Key with another body. |
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. |
503 | The imagery the report prints is briefly unavailable (data.error_code RENDER_UNAVAILABLE); nothing was billed. Retry after Retry-After. |
Check what a report will include
/reports/allowanceBefore creating a report: each report type, whether your key can create it here (available, and reason when not), the sections it prints, and a link to a sample PDF. An API key is billed per report, so billing is metered and quota is null. Send pin and county to ask whether a one-dataset report type would be refused for that parcel (coverage). Free.
Returns what your next report will be. Anything else is a refusal (see Error responses).
Query parameters
| Parameter | Type | Description |
|---|---|---|
pin | string | A parcel id to check, with county. |
county | string | The parcel's county, with pin. e.g. PINELLAS |
Response 200
successbooleanstatusintegertimestampstringdataobjectbillingstringmetered for an API key: billed per report, with no monthly count.quotaobject nullableNull for an API key. A signed-in user's monthly report count.usedintegerlimitintegerremainingintegerresets_atstring nullablereasonstring nullabletypesobjectEvery report type, keyed by its type.Errors
| Status | Meaning |
|---|---|
400 | Only one of pin and county, or a pin that is not a parcel id. |
401 | No API key, an invalid one, or an anonymous token. |
403 | A publishable key, where this route takes a secret one (data.code KEY_CLASS_MISMATCH). |
429 | Too many requests; back off and retry. |
500 | Unexpected server failure. |
Get a report
/reports/{id}A report you created: the same reference the create answered, with a fresh 15-minute download link. Not billed.
Returns the report. Anything else is a refusal (see Error responses).
Path parameters
| Parameter | Type | Description |
|---|---|---|
id required | string | The report id, data.id of the create: rpt_ and a UUID. e.g. rpt_3f2b8c1e-9d4a-4b7e-8f60-2a1c5d9e7b43 |
Response 200
successbooleanstatusintegertimestampstringmetaobjectOn a create only, and absent on an idempotent replay.record_countinteger1: the report this create billed.dataobjectWhich report this is and where to get it. The report's content is in the PDF.idstringrpt_ and a UUID.typestringtitlestringstatusstringredactedbooleantrue for a report made without a paid plan. It has no PDF or preview for its owner.subjectobjectkindstringpinstringcountystringFrom the report's parcel record, else the metadata.county you sent.site_addressstringFrom the report's parcel record, else the metadata.address you sent.created_atstring <date-time>generated_atstring <date-time>download_urlstringDownloads the PDF without a key, for a person to open. A new link on every response; left out when one could not be made.expires_atstring <date-time>When download_url stops working, 15 minutes after this response.preview_urlstring nullablePage 1 as a thumbnail, GET /reports/{id}/preview?size=thumb, once it is stored; null until then.pdf_urlstring nullableGET /reports/{id}/pdf, with your key. It does not expire. Null for a redacted report.Errors
| Status | Meaning |
|---|---|
401 | No API key, an invalid one, or an anonymous token. |
403 | A publishable key, where this route takes a secret one (data.code KEY_CLASS_MISMATCH). |
404 | No such report, or not yours: the same answer. |
429 | Too many requests; back off and retry. |
500 | Unexpected server failure. |
Download a report's PDF
/reports/{id}/pdfThe PDF of a report you created, with your key. This link does not expire, unlike download_url. The first download renders the PDF, which can take several seconds; later ones serve the stored file. Not billed.
Returns the PDF. Anything else is a refusal (see Error responses).
Path parameters
| Parameter | Type | Description |
|---|---|---|
id required | string | The report id, data.id of the create: rpt_ and a UUID. e.g. rpt_3f2b8c1e-9d4a-4b7e-8f60-2a1c5d9e7b43 |
Errors
| Status | Meaning |
|---|---|
304 | Not modified: your If-None-Match matches the stored PDF. |
401 | No API key, an invalid one, or an anonymous token. |
403 | A report made without a paid plan, which has no PDF for its owner, or a publishable key, where this route takes a secret one (data.code KEY_CLASS_MISMATCH). |
404 | No such report, or not yours: the same answer. |
429 | Too many requests; back off and retry. |
500 | Unexpected server failure. |
502 | The PDF could not be rendered; retry later. |
503 | Every renderer is busy (data.error_code RENDER_BUSY); retry after Retry-After. |
Get a report's page-1 preview
/reports/{id}/previewPage 1 of a report you created, as a WebP image. thumb is 320 pixels wide, page 1100. A report's preview_url is set once its preview is stored. Asking for page before then renders the report once, as its first PDF download would. Not billed.
Returns the image. Anything else is a refusal (see Error responses).
Path parameters
| Parameter | Type | Description |
|---|---|---|
id required | string | The report id, data.id of the create: rpt_ and a UUID. e.g. rpt_3f2b8c1e-9d4a-4b7e-8f60-2a1c5d9e7b43 |
Query parameters
| Parameter | Type | Description |
|---|---|---|
size | string | thumb for a list, page to read it. enum: thumb · page |
Errors
| Status | Meaning |
|---|---|
304 | Not modified: your If-None-Match matches the stored image. |
400 | size is not thumb or page. |
401 | No API key, an invalid one, or an anonymous token. |
403 | A report made without a paid plan, which has no preview for its owner, or a publishable key, where this route takes a secret one (data.code KEY_CLASS_MISMATCH). |
404 | No such report, or not yours; or no preview: data.error_code PREVIEW_PENDING (not made yet, ask for page or download the PDF first) or PREVIEW_NEVER (a report older than previews, which never has one). |
429 | Too many requests; back off and retry. |
500 | Unexpected server failure. |
502 | The report could not be rendered; retry later. |
503 | Every renderer is busy (data.error_code RENDER_BUSY); retry after Retry-After. |
Error responses
Every failure has this shape. Failures also carry the problem members type,
title and detail (RFC 9457) and a top-level snake_case code, such as
rate_limited, which is stable and safe to branch on. The existing error field, where a
body has one, is unchanged. The spend-cap 429 adds fields of its own, described under
Monthly spend caps.
{
"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.
If a refusal has a machine-readable reason, branch on that, not on
errormsg. The errormsg text is written for a person and can change.
When a key gate refuses a request, the reason is at data.code: for example,
KEY_CLASS_MISMATCH when a key of the wrong class is used. When a path does not route, the
reason is at the top level.
Every response carries an X-Request-Id, including failures. It identifies
that one request in our logs. Include it when you contact us, so we can find what happened without
searching. To correlate with your own logs, send your own ID. It can be up to 64 letters, digits and
hyphens. We use yours instead of ours, so make it 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 (or code): 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 Manage secret keys. |
Best practices
- Validate all user-supplied input before sending requests.
- Check
meta.record_countto see whether any results came back. - 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. The spend-cap429adds fields of its own (see Monthly spend caps). - Give each integration its own secret key. Rotate it with the rotation steps, which overlap the old and new keys so nothing fails in between.
Contact and support
For questions about the API, higher rate limits or enterprise pricing, contact us.