Crime API Reference
Monthly street-level crime and outcome data published by Police.uk.
Base URL: https://api.govdata.dev/v1
Search crime
Returns paginated, newest-first street-level crime records for analysts, civic-data teams, and local publishers. Combine month, category, force, and LSOA filters to power an LSOA-level community safety dashboard or compare recorded public-order incidents across police forces.
/v1/crime
Waiting for request...
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
month |
string | No | — | Recorded month in YYYY-MM format. |
category |
string | No | — | Exact category label returned by GET /v1/crime/categories. |
force |
string | No | — | Lowercase Police.uk force slug, for example avon-and-somerset-constabulary. |
lsoa_code |
string | No | — | 2021 Lower Layer Super Output Area code; matching is case-insensitive. |
page |
integer | No | 1 | One-based result page. |
per_page |
integer | No | 25 | Results per page; minimum 1, maximum 100. |
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.govdata.dev/v1/crime?month=2026-06&force=avon-and-somerset-constabulary&lsoa_code=E01014399&per_page=1"
uri = URI("https://api.govdata.dev/v1/crime") uri.query = URI.encode_www_form(month: "2026-06", category: "Anti-social behaviour", force: "avon-and-somerset-constabulary", lsoa_code: "E01014399", per_page: 1) req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer YOUR_API_KEY") res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
response = requests.get( "https://api.govdata.dev/v1/crime", headers={"Authorization": "Bearer YOUR_API_KEY"}, params={"month": "2026-06", "category": "Anti-social behaviour", "force": "avon-and-somerset-constabulary", "lsoa_code": "E01014399", "per_page": 2} )
const url = new URL("https://api.govdata.dev/v1/crime"); url.search = new URLSearchParams({ month: "2026-06", category: "Anti-social behaviour", force: "avon-and-somerset-constabulary", lsoa_code: "E01014399", per_page: "1" }); const response = await fetch(url, { headers: { Authorization: "Bearer YOUR_API_KEY" } });
Response
{ "data": [ { "persistent_id": null, "month": "2026-06", "category": "Anti-social behaviour", "force": "avon-and-somerset-constabulary", "force_name": "Avon and Somerset Constabulary", "longitude": -2.492876, "latitude": 51.422716, "location": "On or near Trajectus Way", "location_precision": "Approximate anonymised location", "lsoa_code": "E01014399", "lsoa_name": "Bath and North East Somerset 001A", "last_outcome_category": null, "context": null } ], "meta": { "api_version": "v1", "licence": "Open Government Licence v3.0", "source": "Police.uk", "source_url": "https://data.police.uk/data/", "last_updated": "2026-06", "coverage": { "from": "2010-12", "to": "2026-06" } }, "pagination": { "total": 1, "page": 1, "per_page": 1, "total_pages": 1 } }
longitude and latitude identify Police.uk's anonymised snap-point, not the incident site. lsoa_code and lsoa_name describe that snapped location. The example uses real LSOA E01014399 from the bundled Police.uk development fixture.
Error responses
400 — malformed month. Search has no record-specific 404 or semantic 422 branch; valid filters with no matches return an empty 200 response.
{"error":{"code":"invalid_parameter","message":"month must be YYYY-MM.","documentation_url":"https://docs.govdata.dev/errors/invalid_parameter"}}
Stop and search
Returns paginated stop-and-search encounters exactly as published by participating forces. Researchers and public-interest developers can compare searches under a particular power across forces or inspect how published outcomes vary by object searched for.
/v1/crime/stop-and-search
Waiting for request...
Parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
force |
string | No | — | Force slug derived from the Police.uk archive filename, for example btp. |
from_month |
string | No | — | Inclusive first archive month in YYYY-MM format. |
to_month |
string | No | — | Inclusive last archive month in YYYY-MM format. |
legislation |
string | No | — | Exact legislation text as published. |
object_of_search |
string | No | — | Exact object-of-search text as published. |
page |
integer | No | 1 | One-based result page. |
per_page |
integer | No | 25 | Results per page; minimum 1, maximum 100. |
curl -H "Authorization: Bearer YOUR_API_KEY" "https://api.govdata.dev/v1/crime/stop-and-search?force=btp&from_month=2026-06&to_month=2026-06&object_of_search=Controlled%20drugs"
uri = URI("https://api.govdata.dev/v1/crime/stop-and-search") uri.query = URI.encode_www_form(force: "btp", from_month: "2026-06", to_month: "2026-06", object_of_search: "Controlled drugs") res = Net::HTTP.get_response(uri)
response = requests.get("https://api.govdata.dev/v1/crime/stop-and-search", headers={"Authorization": "Bearer YOUR_API_KEY"}, params={"force": "btp", "from_month": "2026-06", "to_month": "2026-06", "object_of_search": "Controlled drugs"})
const url = new URL("https://api.govdata.dev/v1/crime/stop-and-search"); url.search = new URLSearchParams({ force: "btp", from_month: "2026-06", to_month: "2026-06", object_of_search: "Controlled drugs" }); const response = await fetch(url, { headers: { Authorization: "Bearer YOUR_API_KEY" } });
Response
{ "data": [{ "month": "2026-06", "force": "btp", "type": "Person search", "date": "2026-05-31T23:01:00Z", "part_of_a_policing_operation": null, "policing_operation": null, "latitude": 51.915645, "longitude": -0.67755, "gender": "Male", "age_range": "18-24", "self_defined_ethnicity": "White - English/Welsh/Scottish/Northern Irish/British", "officer_defined_ethnicity": "White", "legislation": "Misuse of Drugs Act 1971 (section 23)", "object_of_search": "Controlled drugs", "outcome": "A no further action disposal", "outcome_linked_to_object_of_search": true, "removal_of_more_than_just_outer_clothing": false }], "meta": {"api_version":"v1","licence":"Open Government Licence v3.0","source":"Police.uk","source_url":"https://data.police.uk/data/"}, "pagination": {"total":197,"page":1,"per_page":25,"total_pages":8} }
The example is from the byte-verbatim 200-row BTP slice extracted from the June 2026 Police.uk archive; three byte-identical source rows converge under the natural digest, leaving 197 records. month is the archive month; date is the encounter timestamp published in the row and can fall just outside it.
Error responses
400 — malformed or reversed month range. There is no record-specific 404 or semantic 422 branch; valid filters with no matches return an empty 200 response.
{"error":{"code":"invalid_parameter","message":"from_month and to_month must be YYYY-MM in ascending order.","documentation_url":"https://docs.govdata.dev/errors/invalid_parameter"}}
Crime categories
Returns the distinct category labels currently present in GovData, for developers building filter menus or validating category values before a crime search. Use it to populate a community-safety widget without hard-coding Police.uk's vocabulary.
/v1/crime/categories
No parameters required.
Waiting for request...
Parameters
| Name | Type | Required | Default | Description |
|---|
This endpoint has no path, query, or pagination parameters.
curl -H "Authorization: Bearer YOUR_API_KEY" "https://api.govdata.dev/v1/crime/categories"
uri = URI("https://api.govdata.dev/v1/crime/categories") req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer YOUR_API_KEY") res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
response = requests.get("https://api.govdata.dev/v1/crime/categories", headers={"Authorization": "Bearer YOUR_API_KEY"})
const response = await fetch("https://api.govdata.dev/v1/crime/categories", { headers: { Authorization: "Bearer YOUR_API_KEY" } });
Response
{ "data": ["Anti-social behaviour", "Burglary", "Anti-social behaviour", "Robbery", "Vehicle crime", "Violence and sexual offences"], "meta": { "api_version": "v1", "licence": "Open Government Licence v3.0", "source": "Police.uk", "source_url": "https://data.police.uk/data/" } }
This is a data-driven list, so labels appear only when the loaded coverage contains at least one record in that category.
Error responses
There are no endpoint-specific 400, 404, or 422 responses because the endpoint accepts no parameters; an empty dataset returns data: [] with HTTP 200.
Data notes
pagination.total and total_pages are PostgreSQL planner estimates for this firehose feed and are approximate; returned crime rows are authoritative.
Coverage and cadence: monthly street-level records for participating police forces in England, Wales and Northern Ireland, with archive coverage back to December 2010. Police.uk publishes processed monthly archives after a lag; GovData imports each archive after publication. Scotland is not covered.
Stop-and-search gaps: PSNI does not publish stop-and-search records in the Police.uk archive, so Northern Ireland has no coverage for this endpoint. Other forces can omit their stop-and-search CSV in individual months; absence means no published file, not necessarily zero searches.
Demographic fields: self-defined ethnicity records the subject's published description, while officer-defined ethnicity records the officer's published assessment. They are distinct source fields, can be blank, and must not be treated as interchangeable or as verified identity data.
Licence and attribution: source Police.uk bulk data, licensed under the Open Government Licence v3.0. Attribute Police.uk when republishing.
Police.uk anonymises locations to nearby snap-points; coordinates are not exact incident locations and an LSOA describes the anonymised point. This is operational police data and is not official statistics. Recording, geocoding, and outcome practices vary between forces, and later publications may revise records.
Some source rows have a blank Crime ID. GovData returns persistent_id: null and uses a deterministic digest only for internal deduplication. Byte-identical blank-ID incidents can therefore merge, and blank-ID crimes cannot be linked to outcomes.