API Documentation

Base URL: https://api.zipcheckup.com/v1

Authentication

Direct requests to standard /v1 endpoints require an API key in the X-API-Key header. Anonymous access is limited to requests made by ZipCheckup pages and the deliberately narrow /v1/public/zip/:zip widget route.

curl -H "X-API-Key: YOUR_API_KEY" \
  https://api.zipcheckup.com/v1/zip/90210

Get a free API key at /api/pricing/. No credit card required.

Rate Limiting

Free tier: 100 requests per day, counted per key when you send one and per IP address when you do not. The daily limit resets at midnight UTC.

PlanDaily Limit
Free100 requests/day
Pro ($49/mo)10,000 requests/day
EnterpriseCustom

Rate limit headers are included in every API response:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per day (e.g., 100)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the limit resets (midnight UTC)

Example headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1711929600

When you exceed the limit, you'll receive a 429 Too Many Requests response with an upgrade URL:

{
  "error": { "message": "Rate limit exceeded. Max 100 requests/day.", "status": 429 },
  "limit": 100,
  "reset": "2026-03-26T00:00:00.000Z",
  "upgrade": "https://zipcheckup.com/api/pricing/"
}

Need more than 100 requests/day? Request higher limits.

Endpoints

Get Water Quality Data by ZIP

GET /v1/zip/{zip}

Returns a full Home Safety Profile for a U.S. ZIP code: versioned multi-source score and coverage, plus separately typed water, contaminant, violation, and contextual evidence.

ParameterTypeDescription
zipstring5-digit U.S. ZIP code (path parameter)

Example response:

{
  "data": {
    "zip": "90210",
    "city": "Beverly Hills",
    "stateAbbr": "CA",
    "homeSafetyScore": 74,
    "homeSafetyGrade": "B",
    "coverage": "covered",
    "contaminants": [
      {
        "code": "2456",
        "name": "Total Trihalomethanes (TTHM)",
        "category": "Disinfection Byproducts",
        "mcl": 0.08,
        "unit": "mg/L",
        "violationCount": 1,
        "healthBased": false
      }
    ],
    "recentViolations": [...],
    "systems": [...],
    "sourceCoverage": { ... }
  },
  "meta": {
    "schema_version": "2.1.0",
    "source_current_at": null,
    "materialized_at": "2026-07-27T08:56:13.000Z",
    "freshness": { "state": "unknown", "reason": "source_vintage_absent" }
  }
}

contaminants lists the contaminants a violation was recorded for, not measured concentrations. mcl is the EPA limit and violationCount is how many violations reference that contaminant. Measured values, where a report was parsed, live separately in ccrMeasurements.

Get Safety Score Only

GET /v1/zip/{zip}/score

Returns only the safety score, grade, and risk level. Lighter response for dashboards and widgets.

Example response:

{
  "data": {
    "zip": "90210",
    "score": 74,
    "grade": "B",
    "coverage": "covered",
    "series": "home-safety-v4-cross-vertical",
    "modelVersion": "4.0.0",
    "componentScores": { "water-compliance": 96, "radon-zone": 50 },
    "componentStatus": { "water-compliance": "observed" }
  },
  "meta": { "schema_version": "2.1.0", "freshness": { "state": "unknown" } }
}

Get State Summary

GET /v1/state/{state}

Returns a summary for a U.S. state: average score, number of ZIPs, top violations, and worst-scoring ZIPs.

ParameterTypeDescription
statestring2-letter state abbreviation (e.g., CA, NY, TX)

Example response:

{
  "data": {
    "state": "CA",
    "stateName": "California",
    "avgScore": 68,
    "totalZips": 1769,
    "scoreKnownZipCount": 1769,
    "violationKnownZipCount": 1602,
    "violationUnknownZipCount": 167,
    "topViolations": [...]
  },
  "meta": { "schema_version": "2.1.0" }
}

The *KnownZipCount and *UnknownZipCount fields are the denominators behind every state average. A ZIP counted as unknown was not measured; it is not a ZIP measured as zero.

Get National Rankings

GET /v1/rankings

Returns a ranked list of ZIP codes by water quality score.

ParameterTypeDefaultDescription
limitinteger50Number of results (max 500)
orderstringdescdesc = best first, asc = worst first
statestringallFilter by 2-letter state code

Get Contaminant Reference

GET /v1/contaminant/{code}

Returns reference information about a contaminant: name, EPA MCL, health effects, and common sources.

ParameterTypeDescription
codestringContaminant code (from ZIP endpoint response)

Bulk ZIP Lookup Pro+

A ZIP the API cannot answer for is returned in unavailable with a typed status rather than as an empty record in results: no_profile, contract_failed and upstream_unavailable are different facts. returned counts what is in results. Quota is charged one request per ZIP, and a batch spanning several data vintages reports freshness.state: "unknown" rather than one timestamp true of none of them.

POST /v1/bulk/zip

Query up to 100 ZIP codes in a single request. Returns full versioned Home Safety Profile objects with separately typed evidence. Pro and Enterprise only.

Body ParameterTypeDescription
zipsstring[]Array of 5-digit ZIP codes (max 100)
fieldsstring[]Optional. Fields to include (e.g., ["score","grade","contaminants"])

Example request:

curl -X POST https://api.zipcheckup.com/v1/bulk/zip \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"zips": ["90210","10001","60601"]}'

Example response:

{
  "data": {
    "requested": 3,
    "returned": 2,
    "results": [ { "zip": "90210", "data": { ... } } ],
    "unavailable": [
      {
        "zip": "00000",
        "status": "no_profile",
        "reason": "No ZipCheckup profile exists for this ZIP code."
      }
    ]
  },
  "meta": { "freshness": { "state": "unknown", "reason": "bulk_mixed_vintage" } }
}

Response Format

All responses are JSON with the following envelope:

// Success
{
  "data": { ... },
  "meta": {
    "source": "ZipCheckup multi-source civic data; see data.sourceCoverage",
    "schema_version": "2.1.0",
    "source_current_at": null,
    "materialized_at": "2026-07-27T08:56:13.000Z",
    "freshness": { "state": "unknown", "reason": "source_vintage_absent" }
  }
}

// Error
{
  "error": {
    "message": "No data found for ZIP 00000",
    "status": 404
  }
}

There is no ok field. A successful response has data and meta; an error has error with message and status. meta.source_current_at is null when the upstream source publishes no vintage, which is not the same as the data being current.

Pro and Enterprise plans can add ?format=csv to the ZIP, score, state, rankings, contaminant and county endpoints. A null renders as the literal #N/A rather than an empty cell, so a value that was never measured cannot be summed as if it were zero; a measured zero stays 0. Schema version and the null token travel in X-ZipCheckup-* headers, since CSV has no envelope.

Error Codes

HTTP StatusError CodeDescription
400BAD_REQUESTInvalid parameters (e.g., malformed ZIP code)
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENEndpoint requires a higher plan (e.g., bulk on Free tier)
404NOT_FOUNDNo data for the requested resource
429RATE_LIMITEDRate limit exceeded. Check X-RateLimit-Reset header
500INTERNAL_ERRORSomething went wrong on our end

Versioning

The API is versioned via the URL path (/v1/). We will not make breaking changes within a version. New fields may be added to responses at any time - your code should handle unknown fields gracefully.

When a new version is released, the previous version will be supported for at least 12 months.

Get Your API Key