OpenCMA Public API
A free, key-free REST API for Ohio and California property data, nightlife scores, and map deep-links. No authentication required.
Base URL: /api/v1 · All endpoints return JSON · CORS open (*)
1. Overview
The OpenCMA public API exposes two endpoints under /api/v1. No API key, no account, no token. Every response is served with an open CORS header (Access-Control-Allow-Origin: *), so you can call it directly from a browser, a server, or an AI agent.
/api/v1AuthNoneCORSOpen (*)Rate limit60 requests / minute / IPEdge cacheSuccessful GETs cached 24 hoursFormatJSON (all responses)Responses include rate-limit headers on every request. On a 429 you will also receive a Retry-After header. See § Rate limits for full details.
2. GET /api/v1/nightlife
Covered cities onlyReturns a nightlife score for a geographic point from precomputed grid data. Coverage is limited to four metro areas; queries outside coverage return a 404.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
lat | number | One of lat/lng or address | Latitude (WGS 84, decimal degrees) |
lng | number | One of lat/lng or address | Longitude (WGS 84, decimal degrees) |
address | string | One of lat/lng or address | Street address; geocoded server-side when lat/lng omitted |
band | 18 | 20 | 25 | 30 | No (default 20) | Peer age cohort for the nightlife score. Selects the age-demographic component used in the composite formula. |
Response
{
"query": { "lat": 41.4993, "lng": -81.6919, "band": 20 },
"city": { "slug": "cleveland", "name": "Greater Cleveland, OH" },
"cell": { "lat": 41.4992, "lng": -81.6918, "distanceMiles": 0.01 },
"components": { "go": 0.91, "stay": 0.88, "p18": 0.9, "p20": 1, "p25": 0.98, "p30": 0.87 },
"composite": 80,
"tier": "Vibrant",
"nearestCluster": {
"name": "Downtown Cleveland",
"lat": 41.4993,
"lng": -81.6919,
"distanceMiles": 0
}
}Field Notes
| Field | Notes |
|---|---|
components | Each value is 0–1. go = venue density score; stay = open-late score; p18/p20/p25/p30 = age-band demographic weight. |
composite | 0–100. Formula: round(go × stay × p<band> × 100) |
tier | One of: Vibrant (≥ 80), Strong (≥ 65), Solid (≥ 50), Some options (≥ 35), Quiet (≥ 20), Sleepy (< 20) |
nearestCluster | Named nightlife district nearest to the queried point. null if no named cluster detected. |
cell | Center of the precomputed grid cell that contains the queried point. distanceMiles is the snap distance from query point to cell center. |
Covered Cities
| Slug | Name |
|---|---|
cleveland | Greater Cleveland, OH |
columbus | Greater Columbus, OH |
bay-area | SF Bay Area (SJ to SF + East Bay), CA |
seattle | Greater Seattle, WA |
Error Responses
| Status | Body (JSON) | Cause |
|---|---|---|
| 400 | {"error":"lat & lng or address required"} | Neither lat/lng nor address provided |
| 404 | {"error":"Address not found"} | Geocoder returned no results for the given address |
| 404 | See below | Point is outside all covered cities |
{
"error": "No nightlife coverage for this location",
"supportedRegions": [
"SF Bay Area (SJ to SF + East Bay), CA",
"Greater Columbus, OH",
"Greater Cleveland, OH",
"Greater Seattle, WA"
],
"hint": "Pass lat/lng or an address inside a covered city: cleveland, columbus, bay-area, seattle."
}3. GET /api/v1/property
OH & CA onlyReturns a curated property summary for a US street address. Aggregates county assessor data, FEMA flood zones, Census demographics, and more into a single response. Supported states: Ohio and California.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
address | string | Yes | Full US street address, e.g. 1619 Mistletoe Dr, Cleveland, OH |
Response
{
"address": "1619 Mistletoe Dr, Cleveland, OH",
"countyTier": "full",
"location": {
"lat": 41.5,
"lng": -81.7,
"countyFips": "035",
"tractCode": "107101"
},
"parcel": {
"parcelId": "123",
"owner": "JANE DOE",
"yearBuilt": 1920,
"buildingSqft": 1450,
"lotAcres": 0.1,
"beds": 3,
"baths": 1,
"useCode": "R-1",
"useDescription": "single_family"
},
"value": {
"assessedTotal": 120000,
"assessedLand": 20000,
"assessedBuilding": 100000,
"marketTotal": 342000
},
"taxRate": {
"municipal": 0.02,
"schoolDistrict": 0.0125,
"schoolDistrictName": "Cleveland Municipal SD"
},
"scores": {
"location": 80,
"environment": 65,
"valueTrend": 70,
"taxBurden": 66,
"investment": 71
},
"flood": {
"zone": "X",
"riskLevel": "minimal",
"isSpecialFloodHazardArea": false
},
"demographics": {
"medianHouseholdIncome": 55000,
"population": 4000,
"medianAge": 38,
"ownerOccupiedPct": 62
},
"nightlife": {
"composite": 80,
"tier": "Vibrant",
"components": {
"go": 0.91,
"stay": 0.88,
"p18": 0.9,
"p20": 1,
"p25": 0.98,
"p30": 0.87
},
"nearestCluster": {
"name": "Downtown Cleveland",
"distanceMiles": 0.3
}
}
}Field Notes
| Field | Notes |
|---|---|
countyTier | "full" = dedicated county scraper; "basic" = limited/fallback data (Ohio OGRIP statewide parcels, or any California county). Many parcel fields may be null. For California, actual data richness varies by county regardless of this flag. |
value | All amounts in USD. In Ohio, assessed value ≈ 35% of market value (state law). In California, assessed value reflects Prop 13 purchase-price base (often well below market). |
taxRate | Decimal fractions: 0.02 = 2%. municipal and schoolDistrict are Ohio income tax rates; null when no tax applies or data unavailable. |
scores | 0–100, higher is better. Aggregated from public-source data; not a substitute for a licensed appraisal. |
flood.riskLevel | One of: minimal, moderate, high, very_high. Derived from FEMA zone code. |
demographics | US Census ACS tract-level data. null if Census lookup fails. |
nightlife | Present only for addresses in a covered city (Cleveland, Columbus, Bay Area, Seattle). Omitted otherwise. The embedded score is computed at band=20 (20–34 cohort); the full components object is included so callers can recompute other bands. Use /api/v1/nightlife?...&band= for a specific cohort. |
| Numeric fields | null when unknown — do not assume 0. |
Error Responses
| Status | Body (JSON) | Cause |
|---|---|---|
| 400 | {"error":"address required"} | No address parameter provided |
| 404 | {"error":"Address not found"} | Geocoder returned no results for the given address |
| 404 | See below | Address is in a state without property data |
{
"error": "Property data not available for this state",
"supportedRegions": ["OH", "CA"],
"hint": "Use a street address within Ohio or California."
}4. Rate Limits
The API enforces a limit of 60 requests per minute per IP address. Every response includes the following headers:
| Header | Value |
|---|---|
X-RateLimit-Limit | Total requests allowed per window (60) |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Seconds until the window resets |
Retry-After | Seconds to wait before retrying (only on 429) |
Successful GETs are edge-cached for 24 hours, so cached responses do not count against your rate limit. If you hit the limit you will receive an HTTP 429 Too Many Requests with a JSON body: {"error":"Rate limit exceeded"}.
5. Examples
Nightlife — curl
curl "https://<site>/api/v1/nightlife?lat=41.4993&lng=-81.6919"Property — curl
curl "https://<site>/api/v1/property?address=1619+Mistletoe+Dr,+Cleveland,+OH"JavaScript fetch()
// Nightlife score
const nightlife = await fetch(
'https://<site>/api/v1/nightlife?lat=41.4993&lng=-81.6919&band=25'
).then(r => r.json());
console.log(nightlife.composite, nightlife.tier);
// → 80 "Vibrant"
// Property report
const property = await fetch(
'https://<site>/api/v1/property?address=' +
encodeURIComponent('1619 Mistletoe Dr, Cleveland, OH')
).then(r => r.json());
console.log(property.parcel.yearBuilt, property.flood.riskLevel);
// → 1920 "minimal"7. For LLMs & Agents
OpenCMA follows the llms.txt convention for machine-readable API summaries. AI agents and LLM pipelines can fetch a compact, structured description of all endpoints, field semantics, and share-link formats without parsing this HTML page.
Fetch /llms.txt — a machine-readable summary you can fetch and parse — and include it in your system prompt or tool context. It covers both endpoints, all field types and null semantics, the composite formula, tier names, coverage, and share-link format.
# In your agent / system prompt:
const apiDocs = await fetch('https://<site>/llms.txt').then(r => r.text());
// → inject apiDocs into context before calling /api/v1/*Note: OpenCMA data is aggregated from public sources (county assessors, FEMA, US Census, OpenStreetMap). It is informational only and is not a substitute for a licensed appraisal, title search, or professional inspection.