OpenCMA

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.

Base URL/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 only

Returns 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

ParameterTypeRequiredDescription
latnumberOne of lat/lng or addressLatitude (WGS 84, decimal degrees)
lngnumberOne of lat/lng or addressLongitude (WGS 84, decimal degrees)
addressstringOne of lat/lng or addressStreet address; geocoded server-side when lat/lng omitted
band18 | 20 | 25 | 30No (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

FieldNotes
componentsEach value is 0–1. go = venue density score; stay = open-late score; p18/p20/p25/p30 = age-band demographic weight.
composite0–100. Formula: round(go × stay × p<band> × 100)
tierOne of: Vibrant (≥ 80), Strong (≥ 65), Solid (≥ 50), Some options (≥ 35), Quiet (≥ 20), Sleepy (< 20)
nearestClusterNamed nightlife district nearest to the queried point. null if no named cluster detected.
cellCenter of the precomputed grid cell that contains the queried point. distanceMiles is the snap distance from query point to cell center.

Covered Cities

SlugName
clevelandGreater Cleveland, OH
columbusGreater Columbus, OH
bay-areaSF Bay Area (SJ to SF + East Bay), CA
seattleGreater Seattle, WA

Error Responses

StatusBody (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
404See belowPoint 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 only

Returns 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

ParameterTypeRequiredDescription
addressstringYesFull 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

FieldNotes
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.
valueAll 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).
taxRateDecimal fractions: 0.02 = 2%. municipal and schoolDistrict are Ohio income tax rates; null when no tax applies or data unavailable.
scores0–100, higher is better. Aggregated from public-source data; not a substitute for a licensed appraisal.
flood.riskLevelOne of: minimal, moderate, high, very_high. Derived from FEMA zone code.
demographicsUS Census ACS tract-level data. null if Census lookup fails.
nightlifePresent 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 fieldsnull when unknown — do not assume 0.

Error Responses

StatusBody (JSON)Cause
400{"error":"address required"}No address parameter provided
404{"error":"Address not found"}Geocoder returned no results for the given address
404See belowAddress 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:

HeaderValue
X-RateLimit-LimitTotal requests allowed per window (60)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetSeconds until the window resets
Retry-AfterSeconds 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.