Developers usually encounter crime data through a simple product question: "Can we show neighborhood risk for this location?" The input might be a ZIP code, an address, a latitude and longitude, or a map viewport. A good crime score API should make those entry points predictable without forcing your team to reconcile inconsistent location records before building the experience.
CrimeScore's ZIP support is designed for that workflow. A ZIP code lookup gives you a broad area estimate when the user only has a 5-digit ZIP. A coordinate lookup gives you a more precise score for a point. The embedded map gives context when a user needs to inspect nearby block groups rather than read a single number.
Before your first request
Choose your organization in the API key dashboard
and use its secret key. Set CRIMESCORE_API_KEY privately in your
terminal or server environment. Do not put the key in frontend JavaScript,
source control, or a chat prompt. Publishable cs_pub_ keys are for
map embeds; REST requests use a secret cs_secret_ key in the
x-api-key header.
The curl examples require curl 7.76 or newer. They use the production API and each successful call consumes one request from the key's organization. The example locations demonstrate the input format; inspect your actual response for coverage and current values.
1. Look up a ZIP code estimate
Send the five-digit ZIP as a string, preserving any leading zero.
GET /v1/score?zip=10001 resolves a scored Census ZIP Code
Tabulation Area, or ZCTA. It is a broad estimate for that area, not a score
for every address inside it.
curl --fail-with-body --silent --show-error --get \
"https://api.crimescore.io/v1/score" \
--data-urlencode "zip=10001" \
--header "x-api-key: $CRIMESCORE_API_KEY"
A successful ZIP response includes zcta, resolved.zip,
safety_score, safety_grade, components,
and data. The metadata includes model_version,
census_vintage, and block_group_count. A ZIP without
score coverage returns an error; it does not mean the score is zero.
2. Resolve a coordinate to its block group
When a user selects a property or point, send lat and lng
together. Do not include zip in the same request. Coordinate
lookup returns the containing Census block group's score, not a measurement
of the individual property.
curl --fail-with-body --silent --show-error --get \
"https://api.crimescore.io/v1/score" \
--data-urlencode "lat=40.7128" \
--data-urlencode "lng=-74.006" \
--header "x-api-key: $CRIMESCORE_API_KEY"
Coordinate responses use a 12-digit geoid instead of zcta.
Keep that identifier as a string. The resolved object includes
the input coordinates, state abbreviation, and county FIPS code.
If your input is an address, use the public GET /v1/geocode
helper first and confirm the intended match before scoring its coordinates.
Geocoding does not consume a score request.
3. Request contributor details when needed
/v1/score already includes component scores. Use
/v1/score/details for the available contributor explanations.
It accepts the same ZIP or coordinate input and an optional
component: overall, violent_crime,
property_crime, disorder, or frequency.
Omitting the component selects overall.
curl --fail-with-body --silent --show-error --get \
"https://api.crimescore.io/v1/score/details" \
--data-urlencode "zip=10001" \
--data-urlencode "component=property_crime" \
--header "x-api-key: $CRIMESCORE_API_KEY" Details are included on Starter and higher plans, and in evaluation access when that capability is enabled. A successful details call consumes a separate request. Contributors explain model associations, not causes. Compare a component across locations; different components use different benchmarks and should not be ranked against each other.
4. Make the request from a Node.js backend
Save this as crime-score.mjs and run node crime-score.mjs
with Node.js 22 or newer and your environment variable configured.
The example makes one request and keeps HTTP status and the API error code
available to a caller. It does not retry automatically.
const apiKey = process.env.CRIMESCORE_API_KEY;
if (!apiKey) throw new Error("Set CRIMESCORE_API_KEY in your server environment");
const url = new URL("https://api.crimescore.io/v1/score");
url.search = new URLSearchParams({ zip: "10001" }).toString();
const response = await fetch(url, {
headers: { "x-api-key": apiKey },
signal: AbortSignal.timeout(15_000),
});
const result = await response.json().catch(() => null);
if (!response.ok) {
const error = new Error(
"CrimeScore request failed: " + response.status + " " +
(result?.error ?? "HTTP_ERROR")
);
error.status = response.status;
error.code = result?.error ?? "HTTP_ERROR";
throw error;
}
console.log({
geography: result.zcta ?? result.geoid,
safetyScore: result.safety_score,
grade: result.safety_grade,
components: result.components,
modelVersion: result.data.model_version,
censusVintage: result.data.census_vintage,
});
To use coordinates, replace the URL parameters with
new URLSearchParams({ lat: "40.7128", lng: "-74.006" }).
In your application, handle a failed lookup as unavailable data rather than
displaying a score of zero. See the
authentication and error guide
for the difference between access, quota, coverage, and rate-limit errors.
A timeout can occur after a request is charged; check usage before retrying.
Fit the response into your product
- Accept a ZIP code for early search, lead forms, saved searches, or market pages.
- Use the ZIP code score response as a broad estimate and label it that way in the UI.
- When the user selects a property or exact point, call the coordinate endpoint for a block-group score.
- Add an embedded map where users benefit from seeing how nearby areas differ visually.
Keep the geography visible
The API intentionally uses different identifiers for different lookup
modes. Coordinate requests return a geoid because they resolve to a
Census block group. ZIP requests return zcta because they resolve to a
Census ZIP Code Tabulation Area estimate. That distinction helps generated
clients and human readers avoid mixing precise coordinate scores with
broader ZIP code estimates.
A simple rule for product copy: call coordinate results "location scores" and ZIP results "ZIP code estimates." Explain the underlying Census ZCTA once where technical precision matters. Modeled Safety Scores use a 0–100 comparative scale, with higher values representing safer relative standing. They are not official crime rates or guarantees.
Embeds and public previews
CrimeScore supports public map previews and authenticated production embeds. Production embeds use publishable keys configured in the dashboard for domain controls and plan-specific behavior.
Use with your AI coding assistant
Give your assistant the documentation index and OpenAPI contract so it can use the actual routes and response fields. A useful starting prompt is:
Read https://crimescore.io/llms.txt and the linked OpenAPI contract. Build a server-side Node.js integration that accepts either a five-digit ZIP or latitude and longitude, uses CRIMESCORE_API_KEY from the environment, and handles non-2xx responses by HTTP status and API error code. Preserve the returned geography and model metadata. Do not request my secret key in chat or make live API calls without my approval.
If you want an assistant to query locations directly instead of writing an integration, follow the separate CrimeScore MCP setup guide.
Evaluate the integration
Start with the public ZIP lookup page to see the no-account map experience. Then open the API playground to compare ZIP code and coordinate response shapes with your own API key. If you are evaluating alternatives, the comparison pages explain how CrimeScore differs from public map sites and raw crime data APIs.
Creating an account is free and does not require payment. When evaluation access is available, the published offer is 25 credits for 30 days in your initial organization. Check the dashboard for your remaining allowance, expiry, and enabled capabilities. Use a small set of representative locations, inspect the returned geography, and read the coverage and data limitations before choosing a paid plan or Starter trial.