Parsing the City of Minneapolis crime data

To populate the Minneapolis Crime Map, Glass on the Street, I’m parsing data directly from the city of Minneapolis' ArcGIS API. The city provides API access to all of their crime data as well as a bunch of other public city data free of charge without the need for API keys or tokens. All the data is made available from a service called ArcGIS.

Try it out right here. See the last seven days of crime data from the city of Minneapolis

This crime data is part of a larger data set from the city that's publicly available on their Open Data Portal and the API data uses their ArcGIS map service. The base URL for Minneapolis crime data is: https://services.arcgis.com/afSMGVsC7QlRK1kZ/arcgis/rest/services/Crime_Data/FeatureServer/0/

Each part of the URL is an identifier to the city’s crime data in the ArcGIS system and it goes like this:

  • afSMGVsC7QlRK1kZ is the city’s identifier to tell ArcGIS what organization the request is about
  • Crime_Date is the service offered or requested
  • FeatureServer/0 means it’s the first layer of the service

This data comes to the city from the police department. The format is the FBIs National Incident-Based Reporting System(NIBRS) standard. More about the NIBRS standard here. For the MPLS crime stats the data begins back on January 01, 2019 and goes through the present day.

Query

The REST API is set up as query system using a where clause to filter data from the known columns. For Glass on the Street crimes are filtered where the Occurred_Date is in the last seven days. I use the last seven days because the crime stats can be updated after they are posted as well as back dated. So getting the last week allows me to go over the existing dataset to update anything new/updated that comes through. This is also where you can filter by crime type or any other property in the fields list - more on that in the API Explorer.

My full query is here: https://services.arcgis.com/afSMGVsC7QlRK1kZ/arcgis/rest/services/Crime_Data/FeatureServer/0/query?where=Occurred_Date >= TIMESTAMP '2026-10-16 05:00:00' AND Occurred_Date < TIMESTAMP '2026-10-23 05:00:00'&outFields=Case_Number,Address,Occurred_Date,Offense,Offense_Category,Type,Neighborhood,Ward,Precinct,wgsXAnon,wgsYAnon,NIBRS_Code,NIBRS_Crime_Against,Crime_Count&returnGeometry=false&orderByFields=Occurred_Date ASC,OBJECTID ASC&resultOffset=0&resultRecordCount=1000&f=json

Query parameters

These are the query parameters that I'm using to pull back the crime data for Glass on the Street.
Parameter What it does
where A SQL-style filter, such as a date range on Occurred_Date. where=1=1 means "no filter."
outFields The columns to return.
returnGeometry Set to false to skip the geometry object. The coordinates are already in wgsXAnon and wgsYAnon.
orderByFields Sort order. Adding OBJECTID as a tiebreaker keeps paging stable.
resultOffset The row to start from. Add the page size for each following page.
resultRecordCount Rows per page. Stop when a page returns fewer than this.
f The response format, such as json.

Fields

These are the fields that are returned and used for the website.
Field What it is
Case_Number MPD's case ID.
Occurred_Date When the offense happened, in epoch milliseconds (UTC).
Offense MPD's offense name.
Offense_Category The broader NIBRS category.
Type Separates offenses from other record types, such as shots-fired calls.
NIBRS_Code The FBI's NIBRS offense code.
NIBRS_Crime_Against Whether the offense is against a person, property or society.
Crime_Count MPD's offense count for the row.
Neighborhood City neighborhood.
Ward City council ward.
Precinct MPD precinct.
Address Block-range address, not an exact one.
wgsXAnon Anonymized block-midpoint X coordinate, in Web Mercator meters.
wgsYAnon Anonymized block-midpoint Y coordinate, in Web Mercator meters.

Results

Based on the query above, the response is a JSON object with an array of different crimes that include all the fields that were part of the request body;

{
  "exceededTransferLimit": true,
  "features": [
    {
      "attributes": {
        "Case_Number": "26-716709",
        "Address": "0015XX LAUREL AVE",
        "Occurred_Date": 1792129500000,
        "Offense": "Destruction/Damage/Vandalism of Property ",
        "Offense_Category": "Destruction/Damage/Vandalism of Property ",
        "Type": "Crime Offenses (NIBRS)",
        "Neighborhood": "Lowry Hill",
        "Ward": 7,
        "Precinct": 4,
        "wgsXAnon": -10384426.0039823,
        "wgsYAnon": 5617428.35059222,
        "NIBRS_Code": "290",
        "NIBRS_Crime_Against": "Property ",
        "Crime_Count": "1"
      }
    },
    {
      "attributes": {
        "Case_Number": "26-299753",
        "Address": "0004XX 1ST AVE NE",
        "Occurred_Date": 1792132500000,
        "Offense": "Theft of Motor Vehicle Parts or Accessories ",
        "Offense_Category": "Larceny/Theft Offenses ",
        "Type": "Crime Offenses (NIBRS)",
        "Neighborhood": "Nicollet Island - East Bank",
        "Ward": 3,
        "Precinct": 2,
        "wgsXAnon": -10381091.0865404,
        "wgsYAnon": 5619907.02006897,
        "NIBRS_Code": "23G ",
        "NIBRS_Crime_Against": "Property ",
        "Crime_Count": "1"
      }
    }
  ]
}

How we parse it

For Glass on the Street, the data is parsed into a DTO object in C# and then saved to a MySQL database.

  • Occurred is in epoch-millisecond time format, which is then parsed to central US time.
  • Offense is used as our category primary key.
  • wgsXAnon and wgsYAnon are used to place the crime on the map. The data is anonymous and will be set to the middle of the block. If you look at Address it is also an anonymous address that snaps to the block level and not the individual building or house.
  • Case_number refers back to the MPD case file. I don't do anything with this number but I could see it being useful for references in other applications

Revised data

Records post to this API get posted late (days after the crime occurred) and are updated frequently. Sometimes data for the previous day(s) will come back empty. That's because the city has not yet posted data for those days but they will show up eventually.

I found this API because I built Glass on the Street because my car windows kept getting smashed and I wanted a tool to track this type of crime.

Try it yourself and change some of the parameters like Occurred_Date or Offense. To only return car related crimes, change the Offense query to look like this:

(Offense LIKE '%Theft From Motor Vehicle%'
 OR Offense LIKE '%Theft of Motor Vehicle Parts or Accessories%'
 OR Offense LIKE '%Motor Vehicle Theft%'
 OR Offense LIKE '%Destruction/Damage/Vandalism of Property%')

This blog doesn't have a comments section. Reply by email - I'd love to hear what you think.