Skip to main content

Custom Maps API

The Custom Maps API provides programmatic access to department-owned custom maps, floors, zones, and coordinate resolution. All endpoints are scoped to the authenticated user's department.

Base URL: /api/v4/CustomMaps

Authentication

All Custom Maps API endpoints require a valid JWT bearer token. See API Authentication for details.


Enumerations

Map Type

ValueNameDescription
0IndoorBuilding floor plans, arenas, hospitals
1SatelliteCustom or classified satellite imagery
2SchematicEngineered drawings, utility diagrams
3EventTemporary event maps; auto-archived after end date

Zone Type

ValueNameDescription
0RoomGeneral room or enclosed space
1HallwayCorridor or passage
2StairWellStairwell
3ElevatorElevator shaft or lobby
4HazardHazmat storage, chemical area, electrical panel
5StagingAreaICS resource or equipment staging
6MusterPointEvacuation assembly / muster point
7ExitEmergency egress point
8ParkingParking area
9GateEntry/exit gate (events, secured facilities)
10CheckpointSecurity or access checkpoint
11CustomUser-defined zone

Custom Maps

Get All Custom Maps

Returns all active custom maps for the department.

GET /api/v4/CustomMaps/GetCustomMaps

Response: Array of CustomMapResult

[
{
"customMapId": "cm_abc123",
"departmentId": 42,
"name": "Station 4 — Main Building",
"description": "Three-story main station building",
"type": 0,
"boundsTopLeftLat": 40.7128,
"boundsTopLeftLng": -74.0060,
"boundsBottomRightLat": 40.7120,
"boundsBottomRightLng": -74.0050,
"imageUrl": "https://storage.resgrid.com/maps/cm_abc123/base.jpg",
"tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/{z}/{x}/{y}.png",
"defaultZoom": 18,
"minZoom": 15,
"maxZoom": 22,
"isActive": true,
"floorCount": 3,
"addedOn": "2026-02-01T09:00:00Z",
"updatedOn": "2026-03-10T14:30:00Z"
}
]

Get Custom Map

Returns full detail for a specific custom map including all floors and zone summaries.

GET /api/v4/CustomMaps/GetCustomMap/{id}
ParameterTypeLocationDescription
idstringPathCustom map ID

Response: CustomMapDetailResult

{
"customMapId": "cm_abc123",
"departmentId": 42,
"name": "Station 4 — Main Building",
"description": "Three-story main station building",
"type": 0,
"boundsTopLeftLat": 40.7128,
"boundsTopLeftLng": -74.0060,
"boundsBottomRightLat": 40.7120,
"boundsBottomRightLng": -74.0050,
"imageUrl": "https://storage.resgrid.com/maps/cm_abc123/base.jpg",
"tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/{z}/{x}/{y}.png",
"defaultZoom": 18,
"minZoom": 15,
"maxZoom": 22,
"isActive": true,
"addedOn": "2026-02-01T09:00:00Z",
"updatedOn": "2026-03-10T14:30:00Z",
"floors": [
{
"customMapFloorId": "cmf_001",
"floorNumber": 1,
"name": "1st Floor",
"imageUrl": "https://storage.resgrid.com/maps/cm_abc123/floor1.jpg",
"tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/f1/{z}/{x}/{y}.png",
"sortOrder": 1,
"isDefault": true,
"elevationFt": 0.0,
"zoneCount": 12
}
]
}

Save Custom Map

Creates a new custom map. Use multipart/form-data to upload the map image.

POST /api/v4/CustomMaps/SaveCustomMap

Content-Type: multipart/form-data

FieldTypeRequiredDescription
namestringYesDisplay name
descriptionstringNoDescription
typeintYesMap type enum (0–3)
boundsTopLeftLatdecimalYesGeo-bounds top-left latitude
boundsTopLeftLngdecimalYesGeo-bounds top-left longitude
boundsBottomRightLatdecimalYesGeo-bounds bottom-right latitude
boundsBottomRightLngdecimalYesGeo-bounds bottom-right longitude
defaultZoomintNoDefault zoom level (1–22)
minZoomintNoMinimum zoom level
maxZoomintNoMaximum zoom level
eventStartDatestringNoISO 8601 date; Event type only
eventEndDatestringNoISO 8601 date; Event type only
isActiveboolNoDefaults to true
imageFilefileYesPNG, JPG, or SVG — max 50 MB

Response: CustomMapResult (the newly created map, including assigned ID and tile URL once processing completes)


Update Custom Map

Updates an existing custom map. To replace the image, use the imageFile field; omit it to keep the existing image.

PUT /api/v4/CustomMaps/UpdateCustomMap/{id}

Content-Type: multipart/form-data

Same fields as SaveCustomMap. imageFile is optional on update.

Response: CustomMapResult


Delete Custom Map

Permanently deletes a custom map and all associated floors, zones, tile files, and share links.

DELETE /api/v4/CustomMaps/DeleteCustomMap/{id}
ParameterTypeLocationDescription
idstringPathCustom map ID

Response: 200 OK on success.


Floors

Get Floors

Returns all floors for a custom map.

GET /api/v4/CustomMaps/GetFloors/{customMapId}
ParameterTypeLocationDescription
customMapIdstringPathCustom map ID

Response: Array of CustomMapFloorResult

[
{
"customMapFloorId": "cmf_001",
"customMapId": "cm_abc123",
"floorNumber": 1,
"name": "1st Floor",
"imageUrl": "https://storage.resgrid.com/maps/cm_abc123/floor1.jpg",
"tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/f1/{z}/{x}/{y}.png",
"sortOrder": 1,
"isDefault": true,
"elevationFt": 0.0
},
{
"customMapFloorId": "cmf_002",
"customMapId": "cm_abc123",
"floorNumber": 2,
"name": "2nd Floor",
"imageUrl": "https://storage.resgrid.com/maps/cm_abc123/floor2.jpg",
"tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/f2/{z}/{x}/{y}.png",
"sortOrder": 2,
"isDefault": false,
"elevationFt": 12.0
}
]

Save Floor

Adds a new floor to a custom map. Use multipart/form-data.

POST /api/v4/CustomMaps/SaveFloor/{customMapId}

Content-Type: multipart/form-data

FieldTypeRequiredDescription
floorNumberintYesNumeric floor level (negative for sub-levels)
namestringYesFloor display name
sortOrderintNoDisplay sort order in tab bar
isDefaultboolNoSet as the default floor
elevationFtdecimalNoPhysical elevation above sea level in feet
imageFilefileYesFloor plan image (PNG, JPG, SVG — max 50 MB)

Response: CustomMapFloorResult


Update Floor

Updates floor metadata. Omit imageFile to keep the existing floor image.

PUT /api/v4/CustomMaps/UpdateFloor/{floorId}

Content-Type: multipart/form-data

Same fields as SaveFloor. imageFile is optional on update.

Response: CustomMapFloorResult


Delete Floor

Permanently deletes a floor and all of its zones.

DELETE /api/v4/CustomMaps/DeleteFloor/{floorId}
ParameterTypeLocationDescription
floorIdstringPathFloor ID

Response: 200 OK on success.


Zones

Get Zones

Returns all zones for a floor including full polygon GeoJSON.

GET /api/v4/CustomMaps/GetZones/{floorId}
ParameterTypeLocationDescription
floorIdstringPathFloor ID

Response: Array of CustomMapZoneResult

[
{
"customMapZoneId": "cmz_001",
"customMapFloorId": "cmf_001",
"name": "Building 1, Room 405a",
"zoneType": 0,
"polygonGeoJson": "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.0058,40.7126],[-74.0056,40.7126],[-74.0056,40.7124],[-74.0058,40.7124],[-74.0058,40.7126]]]},\"properties\":{}}",
"color": "#3388ff",
"metadata": "{\"capacity\": \"20\", \"wing\": \"North\"}",
"elevationFt": 45.0,
"isSearchable": true,
"isActive": true
}
]

Get Zone

Returns a single zone by ID.

GET /api/v4/CustomMaps/GetZone/{zoneId}
ParameterTypeLocationDescription
zoneIdstringPathZone ID

Response: CustomMapZoneResult


Save Zone

Creates a new zone on a floor.

POST /api/v4/CustomMaps/SaveZone/{floorId}

Content-Type: application/json

{
"name": "Building 1, Room 405a",
"zoneType": 0,
"polygonGeoJson": "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.0058,40.7126],[-74.0056,40.7126],[-74.0056,40.7124],[-74.0058,40.7124],[-74.0058,40.7126]]]},\"properties\":{}}}",
"color": "#3388ff",
"metadata": "{\"capacity\": \"20\"}",
"elevationFt": 45.0,
"isSearchable": true,
"isActive": true
}
FieldTypeRequiredDescription
namestringYesHuman-readable zone name; used as call location
zoneTypeintYesZone type enum (0–11)
polygonGeoJsonstringYesGeoJSON Feature with a Polygon geometry; coordinates must be real-world lat/lng
colorstringNoHex color code for polygon fill
metadatastringNoJSON string of key/value metadata
elevationFtdecimalNoZone elevation in feet
isSearchableboolNoInclude zone in map search
isActiveboolNoDefaults to true

Response: CustomMapZoneResult


Update Zone

Updates an existing zone. All fields may be updated including the polygon geometry.

PUT /api/v4/CustomMaps/UpdateZone/{zoneId}

Content-Type: application/json

Same body shape as SaveZone.

Response: CustomMapZoneResult


Delete Zone

Permanently deletes a zone.

DELETE /api/v4/CustomMaps/DeleteZone/{zoneId}
ParameterTypeLocationDescription
zoneIdstringPathZone ID

Response: 200 OK on success.


Coordinate Resolution

Resolve Coordinate to Zone

Given a custom map, floor, and real-world latitude/longitude, performs a point-in-polygon test against all active zones on that floor and returns the matching zone name.

This endpoint is used by mobile apps and CAD integrations to automatically resolve a GPS position to a room or area name.

POST /api/v4/CustomMaps/ResolveCoordinate

Content-Type: application/json

{
"customMapId": "cm_abc123",
"floorId": "cmf_001",
"latitude": 40.7125,
"longitude": -74.0057
}
FieldTypeRequiredDescription
customMapIdstringYesCustom map ID
floorIdstringYesFloor ID to test against
latitudedecimalYesPoint latitude
longitudedecimalYesPoint longitude

Response: CoordinateResolveResult

{
"matched": true,
"customMapZoneId": "cmz_001",
"zoneName": "Building 1, Room 405a",
"zoneType": 0,
"floorName": "1st Floor",
"mapName": "Station 4 — Main Building"
}

If no zone matches:

{
"matched": false,
"customMapZoneId": null,
"zoneName": null,
"zoneType": null,
"floorName": "1st Floor",
"mapName": "Station 4 — Main Building"
}

Map Metadata in GetMapDataAndMarkers

The existing map data endpoint includes a customMaps collection in its response when the department has active custom maps. This allows map clients to discover available custom maps without a separate API call.

GET /api/v4/Mapping/GetMapDataAndMarkers

The response includes:

{
"customMaps": [
{
"customMapId": "cm_abc123",
"name": "Station 4 — Main Building",
"type": 0,
"boundsTopLeftLat": 40.7128,
"boundsTopLeftLng": -74.0060,
"boundsBottomRightLat": 40.7120,
"boundsBottomRightLng": -74.0050,
"defaultZoom": 18,
"tileUrlTemplate": "https://storage.resgrid.com/tiles/cm_abc123/{z}/{x}/{y}.png",
"floorCount": 3,
"defaultFloorId": "cmf_001"
}
]
}

Offline Sync (Mobile)

Mobile clients use a version-check endpoint before deciding whether to download map data.

Get Map Version Manifest

Returns a lightweight manifest of all custom maps and their current versions. Mobile apps compare this against their local cache and download only what has changed.

GET /api/v4/CustomMaps/GetVersionManifest

Response: CustomMapVersionManifest

{
"generatedAt": "2026-03-14T08:00:00Z",
"maps": [
{
"customMapId": "cm_abc123",
"mapVersion": 7,
"floors": [
{
"customMapFloorId": "cmf_001",
"floorVersion": 3,
"zoneVersion": 12
},
{
"customMapFloorId": "cmf_002",
"floorVersion": 1,
"zoneVersion": 5
}
]
}
]
}

The mobile app:

  1. Calls GetVersionManifest on launch or foreground.
  2. Compares each mapVersion, floorVersion, and zoneVersion to the locally cached values.
  3. For any entry with a higher server version, downloads the relevant floor image and/or zone GeoJSON.
  4. Stores updated data in local storage for offline use.

Error Responses

All endpoints return standard HTTP status codes with a JSON error body:

{
"error": "CustomMapNotFound",
"message": "No custom map with ID 'cm_xyz' was found for this department.",
"statusCode": 404
}
Status CodeMeaning
400Invalid request body or missing required fields
401Missing or expired authentication token
403Caller does not have permission for this operation
404Requested map, floor, or zone not found for this department
413Uploaded image exceeds the 50 MB limit
422Invalid GeoJSON polygon; polygon must be closed and have ≥ 3 vertices
500Internal server error