This directory contains the OpenAPI specification for the Police Incident Management System REST API.
openapi.yaml- Complete OpenAPI 3.0.3 specification for all API endpoints
You can view and interact with the API specification using several tools:
If you have Swagger UI installed or running:
# Using Docker
docker run -p 8080:8080 -e SWAGGER_JSON=/api/openapi.yaml -v $(pwd)/doc/api:/api swaggerapi/swagger-ui
# Then open http://localhost:8080 in your browserUse the online Swagger Editor:
- Go to https://editor.swagger.io/
- File → Import File → Select
openapi.yaml - View and edit the specification
Import the OpenAPI specification into Postman:
- Open Postman
- Import → File → Select
openapi.yaml - All endpoints will be imported as a collection
Import the OpenAPI specification into Insomnia:
- Open Insomnia
- Create → Import → From File → Select
openapi.yaml - All endpoints will be imported
Generate beautiful documentation with Redoc:
# Using Docker
docker run -p 8080:80 -v $(pwd)/doc/api:/usr/share/nginx/html/api:ro redocly/redoc
# Then open http://localhost:8080/api/openapi.yamlValidate the OpenAPI specification:
# Using swagger-cli
npm install -g @apidevtools/swagger-cli
swagger-cli validate doc/api/openapi.yaml
# Using openapi-cli
npm install -g @redocly/cli
redocly lint doc/api/openapi.yamlThe Police System API is an event-driven REST API where:
- All operations produce events to Kafka
- Events represent requests/commands, not state changes
- No state reconstruction in the edge layer
- All operations are asynchronous via Kafka events
- Local:
http://localhost:8080/api - Production:
https://api.policesystem.example.com/api
Currently, the API does not require authentication (to be implemented in future increments).
All successful responses return JSON with appropriate status codes:
201 Created- Resource created200 OK- Request processed successfully400 Bad Request- Validation error404 Not Found- Resource not found409 Conflict- Resource conflict (e.g., duplicate badge number)
Error responses follow this structure:
{
"error": "Bad Request",
"message": "Validation failed",
"details": [
"badgeNumber is required",
"email must be a valid email address"
]
}The API supports synchronous resource existence checks via NATS request-response queries to projections. This enables immediate feedback on resource state:
Returned when attempting to update, modify, or perform operations on a resource that doesn't exist in the projection.
Example 404 Response:
{
"error": "Not Found",
"message": "Officer not found: BADGE-999",
"details": []
}Endpoints that return 404:
PUT /officers/{badgeNumber}- When officer doesn't existPATCH /officers/{badgeNumber}/status- When officer doesn't existPUT /incidents/{incidentId}- When incident doesn't existPOST /incidents/{incidentId}/dispatch- When incident doesn't existPUT /activities/{activityId}- When activity doesn't existPATCH /assignments/{assignmentId}/status- When assignment doesn't exist- Similar update/status change endpoints for other domains
Returned when attempting to create a resource that already exists (e.g., duplicate badge number).
Example 409 Response:
{
"error": "Conflict",
"message": "Officer with badge number already exists: 12345",
"details": []
}Endpoints that return 409:
POST /officers- When badge number already exists
Projections are eventually consistent. A resource may not appear in the projection immediately after creation. The edge queries projections synchronously via NATS, so:
- Recently created resources may return 404 if queried too quickly after creation
- This is expected behavior in an eventually consistent system
- Clients should handle 404 responses appropriately (e.g., retry after a short delay)
- The resource will be available once the projection has processed the creation event
Best Practice: When creating a resource and then immediately updating it, add a small delay or use the projection's REST API to verify the resource exists before updating.
POST /officers- Register a new officerPUT /officers/{badgeNumber}- Update officer informationPATCH /officers/{badgeNumber}/status- Change officer status
POST /vehicles- Register a new vehiclePUT /vehicles/{unitId}- Update vehicle informationPATCH /vehicles/{unitId}/status- Change vehicle status
POST /units- Create a new unitPUT /units/{unitId}- Update unit informationPATCH /units/{unitId}/status- Change unit status
POST /persons- Register a new personPUT /persons/{personId}- Update person information
POST /locations- Create a new locationPUT /locations/{locationId}- Update location informationPOST /incidents/{incidentId}/locations- Link location to incidentDELETE /incidents/{incidentId}/locations/{locationId}- Unlink location from incidentPOST /calls/{callId}/locations- Link location to callDELETE /calls/{callId}/locations/{locationId}- Unlink location from call
POST /incidents- Report a new incidentPUT /incidents/{incidentId}- Update incident informationPATCH /incidents/{incidentId}/status- Change incident statusPOST /incidents/{incidentId}/dispatch- Dispatch incidentPOST /incidents/{incidentId}/arrive- Arrive at incidentPOST /incidents/{incidentId}/clear- Clear incident
POST /calls- Receive a new callPUT /calls/{callId}- Update call informationPATCH /calls/{callId}/status- Change call statusPOST /calls/{callId}/dispatch- Dispatch callPOST /calls/{callId}/arrive- Arrive at callPOST /calls/{callId}/clear- Clear callPOST /calls/{callId}/incidents- Link call to incidentPOST /calls/{callId}/dispatches- Link call to dispatch
POST /activities- Start a new activityPUT /activities/{activityId}- Update activity informationPATCH /activities/{activityId}/status- Change activity statusPOST /activities/{activityId}/complete- Complete activityPOST /activities/{activityId}/incidents- Link activity to incident
POST /assignments- Create a new assignmentPATCH /assignments/{assignmentId}/status- Change assignment statusPOST /assignments/{assignmentId}/complete- Complete assignmentPOST /assignments/{assignmentId}/dispatches- Link assignment to dispatchPOST /assignments/{assignmentId}/resources- Assign resource to assignmentDELETE /assignments/{assignmentId}/resources/{resourceId}- Unassign resource from assignmentPATCH /assignments/{assignmentId}/resources/{resourceId}/status- Change resource assignment status
POST /shifts- Start a new shiftPATCH /shifts/{shiftId}/status- Change shift statusPOST /shifts/{shiftId}/end- End shiftPOST /shifts/{shiftId}/shift-changes- Record shift changePUT /shifts/{shiftId}/officers/{badgeNumber}- Update officer shiftPOST /shifts/{shiftId}/officers/{badgeNumber}/check-in- Check in officer to shiftPOST /shifts/{shiftId}/officers/{badgeNumber}/check-out- Check out officer from shift
POST /dispatches- Create a new dispatchPATCH /dispatches/{dispatchId}/status- Change dispatch status
POST /involved-parties- Involve party in incident/call/activityPUT /involved-parties/{involvementId}- Update party involvementPOST /involved-parties/{involvementId}/end- End party involvement
✅ Implemented: 3 projection services expose query APIs for read-only access to projected data. Each projection service runs as a separate deployable service (future K8s pod).
Projection Services:
- Operational Projection:
http://localhost:8081/api/projections/(handles: incidents, calls, dispatches, activities, assignments, involved parties, resource assignments) - Resource Projection:
http://localhost:8082/api/projections/(handles: officers, vehicles, units, persons, locations) - Workforce Projection:
http://localhost:8083/api/projections/(handles: shifts, officer shifts, shift changes)
All projection services support:
GET /{id}- Get single entity by IDGET /{id}/history- Get status change history for entityGET /- List entities with filtering and pagination
Incidents:
GET /api/projections/incidents/{incidentId}- Get incident by IDGET /api/projections/incidents/{incidentId}/history- Get incident status historyGET /api/projections/incidents?status={status}&priority={priority}&page={page}&size={size}- List incidents with filtersGET /api/projections/incidents/{incidentId}/full- Get incident with all related data (composite query)
Calls:
GET /api/projections/calls/{callId}- Get call by IDGET /api/projections/calls/{callId}/history- Get call status historyGET /api/projections/calls?status={status}&priority={priority}&incidentId={id}&page={page}&size={size}- List calls with filters
Dispatches:
GET /api/projections/dispatches/{dispatchId}- Get dispatch by IDGET /api/projections/dispatches/{dispatchId}/history- Get dispatch status historyGET /api/projections/dispatches?status={status}&callId={id}&page={page}&size={size}- List dispatches with filters
Activities:
GET /api/projections/activities/{activityId}- Get activity by IDGET /api/projections/activities/{activityId}/history- Get activity status historyGET /api/projections/activities?status={status}&activityType={type}&incidentId={id}&callId={id}&page={page}&size={size}- List activities with filters
Assignments:
GET /api/projections/assignments/{assignmentId}- Get assignment by IDGET /api/projections/assignments/{assignmentId}/history- Get assignment status historyGET /api/projections/assignments?status={status}&dispatchId={id}&incidentId={id}&callId={id}&page={page}&size={size}- List assignments with filters
Involved Parties:
GET /api/projections/involved-parties/{involvementId}- Get involved party by IDGET /api/projections/involved-parties?personId={id}&incidentId={id}&callId={id}&activityId={id}&page={page}&size={size}- List involved parties with filters
Resource Assignments:
GET /api/projections/resource-assignments/{id}- Get resource assignment by IDGET /api/projections/resource-assignments?assignmentId={id}&resourceId={id}&resourceType={type}&page={page}&size={size}- List resource assignments with filters
Officers:
GET /api/projections/officers/{badgeNumber}- Get officer by badge numberGET /api/projections/officers/{badgeNumber}/history- Get officer status historyGET /api/projections/officers?status={status}&rank={rank}&page={page}&size={size}- List officers with filters
Vehicles:
GET /api/projections/vehicles/{unitId}- Get vehicle by unit IDGET /api/projections/vehicles/{unitId}/history- Get vehicle status historyGET /api/projections/vehicles?status={status}&page={page}&size={size}- List vehicles with filters
Units:
GET /api/projections/units/{unitId}- Get unit by unit IDGET /api/projections/units/{unitId}/history- Get unit status historyGET /api/projections/units?status={status}&page={page}&size={size}- List units with filters
Persons:
GET /api/projections/persons/{personId}- Get person by person IDGET /api/projections/persons?page={page}&size={size}- List persons with pagination
Locations:
GET /api/projections/locations/{locationId}- Get location by location IDGET /api/projections/locations?page={page}&size={size}- List locations with pagination
Shifts:
GET /api/projections/shifts/{shiftId}- Get shift by shift IDGET /api/projections/shifts/{shiftId}/history- Get shift status historyGET /api/projections/shifts?status={status}&shiftType={type}&page={page}&size={size}- List shifts with filters
Officer Shifts:
GET /api/projections/officer-shifts/{id}- Get officer shift by IDGET /api/projections/officer-shifts?shiftId={id}&badgeNumber={badge}&page={page}&size={size}- List officer shifts with filters
Shift Changes:
GET /api/projections/shift-changes/{shiftChangeId}- Get shift change by shift change IDGET /api/projections/shift-changes?shiftId={id}&page={page}&size={size}- List shift changes with filters
Note: Projection services are eventually consistent. Data may lag slightly behind command operations.
All API endpoints produce events to Kafka topics:
- Officer operations →
officer-eventstopic - Vehicle operations →
vehicle-eventstopic - Unit operations →
unit-eventstopic - Person operations →
person-eventstopic - Location operations →
location-eventstopic - Incident operations →
incident-eventstopic - Call operations →
call-eventstopic - Activity operations →
activity-eventstopic - Assignment operations →
assignment-eventstopic - Shift operations →
shift-eventstopic - Dispatch operations →
dispatch-eventstopic - Resource assignment operations →
resource-assignment-eventstopic - Involved party operations →
involved-party-eventstopic - Officer shift operations →
officer-shift-eventstopic
curl -X POST http://localhost:8080/api/officers \
-H "Content-Type: application/json" \
-d '{
"badgeNumber": "12345",
"firstName": "John",
"lastName": "Doe",
"rank": "Officer",
"email": "john.doe@police.gov",
"phoneNumber": "555-0100",
"hireDate": "2020-01-15",
"status": "Active"
}'curl -X POST http://localhost:8080/api/incidents \
-H "Content-Type: application/json" \
-d '{
"incidentId": "INC-001",
"incidentNumber": "2024-001",
"priority": "High",
"status": "Reported",
"reportedTime": "2024-01-15T10:30:00Z",
"description": "Traffic accident at Main St and Oak Ave",
"incidentType": "Traffic"
}'curl -X POST http://localhost:8080/api/incidents/INC-001/dispatch \
-H "Content-Type: application/json" \
-d '{
"dispatchedTime": "2024-01-15T10:35:00Z"
}'All endpoints can be tested using:
- Swagger UI (interactive testing)
- Postman (collection import)
- curl commands (see examples above)
- Any HTTP client
- This specification represents the complete API when all increments are implemented
- Some endpoints may not be available until their corresponding increment is completed
- All timestamps use ISO 8601 format (date-time)
- All dates use ISO 8601 format (date)
- Enum values are case-sensitive