openapi: 3.0.3 info: title: Presence API description: API for gateways, zones, trackers, parser configs, settings, alerts, and tracks. version: 1.0.0 servers: - url: / description: Default server paths: # --- Health --- /health: get: summary: Liveness check operationId: health tags: [Health] responses: '200': description: Service is alive content: application/json: schema: type: object properties: status: type: string example: ok /ready: get: summary: Readiness check (DB connectivity) operationId: ready tags: [Health] responses: '200': description: Service is ready content: application/json: schema: type: object properties: status: type: string example: ready '503': $ref: '#/components/responses/ServiceUnavailable' # --- Gateways --- /reslevis/getGateways: get: summary: List all gateways operationId: getGateways tags: [Gateways] responses: '200': description: List of gateways content: application/json: schema: type: array items: $ref: '#/components/schemas/Gateway' '500': $ref: '#/components/responses/InternalError' /reslevis/postGateway: post: summary: Create a gateway operationId: postGateway tags: [Gateways] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Gateway' responses: '201': description: Gateway created content: application/json: schema: $ref: '#/components/schemas/StatusCreated' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalError' /reslevis/removeGateway/{id}: delete: summary: Delete a gateway by ID operationId: removeGateway tags: [Gateways] parameters: - $ref: '#/components/parameters/PathId' responses: '200': description: Gateway deleted content: application/json: schema: $ref: '#/components/schemas/StatusDeleted' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /reslevis/updateGateway/{id}: put: summary: Update a gateway by ID operationId: updateGateway tags: [Gateways] parameters: - $ref: '#/components/parameters/PathId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Gateway' responses: '200': description: Gateway updated content: application/json: schema: $ref: '#/components/schemas/StatusUpdated' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' # --- Zones --- /reslevis/getZones: get: summary: List all zones operationId: getZones tags: [Zones] responses: '200': description: List of zones content: application/json: schema: type: array items: $ref: '#/components/schemas/Zone' '500': $ref: '#/components/responses/InternalError' /reslevis/postZone: post: summary: Create a zone operationId: postZone tags: [Zones] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Zone' responses: '201': description: Zone created content: application/json: schema: $ref: '#/components/schemas/StatusCreated' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalError' /reslevis/removeZone/{id}: delete: summary: Delete a zone by ID operationId: removeZone tags: [Zones] parameters: - $ref: '#/components/parameters/PathId' responses: '200': description: Zone deleted content: application/json: schema: $ref: '#/components/schemas/StatusDeleted' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /reslevis/updateZone: put: summary: Update a zone operationId: updateZone tags: [Zones] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Zone' responses: '200': description: Zone updated content: application/json: schema: $ref: '#/components/schemas/StatusUpdated' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' # --- Tracker zones --- /reslevis/getTrackerZones: get: summary: List all tracker zones operationId: getTrackerZones tags: [TrackerZones] responses: '200': description: List of tracker zones content: application/json: schema: type: array items: $ref: '#/components/schemas/TrackerZone' '500': $ref: '#/components/responses/InternalError' /reslevis/postTrackerZone: post: summary: Create a tracker zone operationId: postTrackerZone tags: [TrackerZones] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TrackerZone' responses: '201': description: Tracker zone created content: application/json: schema: $ref: '#/components/schemas/StatusCreated' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalError' /reslevis/removeTrackerZone/{id}: delete: summary: Delete a tracker zone by ID operationId: removeTrackerZone tags: [TrackerZones] parameters: - $ref: '#/components/parameters/PathId' responses: '200': description: Tracker zone deleted content: application/json: schema: $ref: '#/components/schemas/StatusDeleted' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /reslevis/updateTrackerZone: put: summary: Update a tracker zone operationId: updateTrackerZone tags: [TrackerZones] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TrackerZone' responses: '200': description: Tracker zone updated content: application/json: schema: $ref: '#/components/schemas/StatusUpdated' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' # --- Trackers --- /reslevis/getTrackers: get: summary: List all trackers operationId: getTrackers tags: [Trackers] responses: '200': description: List of trackers content: application/json: schema: type: array items: $ref: '#/components/schemas/Tracker' '500': $ref: '#/components/responses/InternalError' /reslevis/postTracker: post: summary: Create a tracker operationId: postTracker tags: [Trackers] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Tracker' responses: '201': description: Tracker created content: application/json: schema: $ref: '#/components/schemas/StatusCreated' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalError' /reslevis/removeTracker/{id}: delete: summary: Delete a tracker by ID operationId: removeTracker tags: [Trackers] parameters: - $ref: '#/components/parameters/PathId' responses: '200': description: Tracker deleted content: application/json: schema: $ref: '#/components/schemas/StatusDeleted' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /reslevis/updateTracker: put: summary: Update a tracker operationId: updateTracker tags: [Trackers] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Tracker' responses: '200': description: Tracker updated content: application/json: schema: $ref: '#/components/schemas/StatusUpdated' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' # --- Parser configs (beacons) --- /configs/beacons: get: summary: List all beacon parser configs operationId: listParserConfigs tags: [ParserConfigs] responses: '200': description: List of parser configs content: application/json: schema: type: array items: $ref: '#/components/schemas/ParserConfig' '500': $ref: '#/components/responses/InternalError' post: summary: Create a beacon parser config operationId: addParserConfig tags: [ParserConfigs] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ParserConfig' responses: '201': description: Parser config created content: application/json: schema: $ref: '#/components/schemas/StatusCreated' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalError' /configs/beacons/{id}: put: summary: Update a beacon parser config by name (id) operationId: updateParserConfig tags: [ParserConfigs] parameters: - name: id in: path required: true description: Config name (identifier) schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ParserConfig' responses: '200': description: Parser config updated content: application/json: schema: $ref: '#/components/schemas/StatusUpdated' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' delete: summary: Delete a beacon parser config by name (id) operationId: deleteParserConfig tags: [ParserConfigs] parameters: - name: id in: path required: true description: Config name (identifier) schema: type: string responses: '200': description: Parser config deleted content: application/json: schema: $ref: '#/components/schemas/StatusDeleted' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' # --- Settings --- /reslevis/settings: get: summary: List settings operationId: getSettings tags: [Settings] responses: '200': description: List of settings (typically one row) content: application/json: schema: type: array items: $ref: '#/components/schemas/Settings' '500': $ref: '#/components/responses/InternalError' patch: summary: Partially update settings operationId: updateSettings tags: [Settings] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SettingsUpdate' responses: '200': description: Settings updated content: application/json: schema: $ref: '#/components/schemas/StatusUpdated' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalError' # --- Alerts --- /reslevis/alerts: get: summary: List all alerts operationId: listAlerts tags: [Alerts] responses: '200': description: List of alerts content: application/json: schema: type: array items: $ref: '#/components/schemas/Alert' '500': $ref: '#/components/responses/InternalError' /reslevis/alerts/{id}: get: summary: Get alert by ID operationId: getAlertById tags: [Alerts] parameters: - $ref: '#/components/parameters/PathId' responses: '200': description: Single alert content: application/json: schema: $ref: '#/components/schemas/Alert' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' patch: summary: Update alert status operationId: updateAlertStatus tags: [Alerts] parameters: - $ref: '#/components/parameters/PathId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AlertStatusUpdate' responses: '200': description: Alert status updated content: application/json: schema: $ref: '#/components/schemas/StatusUpdated' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' delete: summary: Delete alert by ID operationId: deleteAlert tags: [Alerts] parameters: - $ref: '#/components/parameters/PathId' responses: '200': description: Alert deleted content: application/json: schema: $ref: '#/components/schemas/StatusDeleted' '500': $ref: '#/components/responses/InternalError' # --- Aggregated health (location, decoder, bridge, kafka, database) --- /reslevis/health: get: summary: Aggregated health status description: Returns health from location, decoder, bridge services plus server-checked Kafka and database status. operationId: getAggregatedHealth tags: [Health] responses: '200': description: Aggregated health (location, decoder, bridge, kafka, database) content: application/json: schema: $ref: '#/components/schemas/AggregatedHealth' '500': $ref: '#/components/responses/InternalError' # --- Tracks --- /reslevis/getTracks/{id}: get: summary: List tracks for a tracker UUID operationId: getTracks tags: [Tracks] parameters: - name: id in: path required: true description: Tracker UUID schema: type: string - name: limit in: query description: Max number of tracks to return (default 10) schema: type: integer default: 10 - name: from in: query description: Start time (RFC3339) schema: type: string format: date-time - name: to in: query description: End time (RFC3339) schema: type: string format: date-time responses: '200': description: List of tracks content: application/json: schema: type: array items: $ref: '#/components/schemas/Track' '500': $ref: '#/components/responses/InternalError' components: parameters: PathId: name: id in: path required: true description: Resource ID schema: type: string responses: BadRequest: description: Invalid request (e.g. invalid JSON) content: application/json: schema: $ref: '#/components/schemas/Error' example: error: bad_request message: invalid request body NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' example: error: not_found message: resource not found InternalError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/Error' example: error: internal_error message: failed to complete operation ServiceUnavailable: description: Service not ready (e.g. DB unavailable) content: application/json: schema: $ref: '#/components/schemas/Error' example: error: not_ready message: database not available schemas: Error: type: object required: [error] properties: error: type: string description: Error code (bad_request, not_found, internal_error, not_ready) message: type: string description: Human-readable message StatusCreated: type: object properties: status: type: string example: created StatusUpdated: type: object properties: status: type: string example: updated StatusDeleted: type: object properties: status: type: string example: deleted Gateway: type: object properties: id: { type: string } name: { type: string } mac: { type: string } status: { type: string } model: { type: string } ip: { type: string } position: { type: string } x: { type: number, format: float } y: { type: number, format: float } notes: { type: string } floor: { type: string } building: { type: string } Zone: type: object properties: id: { type: string } name: { type: string } groups: type: array items: { type: string } floor: { type: string } building: { type: string } TrackerZone: type: object properties: id: { type: string } zoneList: type: array items: { type: string } tracker: { type: string } days: { type: string } time: { type: string } Tracker: type: object properties: id: { type: string } name: { type: string } mac: { type: string } status: { type: string } model: { type: string } ip: { type: string } position: { type: string } x: { type: number, format: float } y: { type: number, format: float } notes: { type: string } floor: { type: string } building: { type: string } battery: { type: integer } batteryThreshold: { type: integer } temperature: { type: integer } ParserConfig: type: object description: Beacon parser configuration (name is the primary key) properties: name: { type: string } min: { type: integer } max: { type: integer } pattern: type: array items: { type: string } configs: type: object additionalProperties: $ref: '#/components/schemas/ParserConfigEntry' ParserConfigEntry: type: object properties: length: { type: integer } offset: { type: integer } order: { type: string } Settings: type: object properties: ID: { type: integer } current_algorithm: { type: string } location_confidence: { type: integer } last_seen_threshold: { type: integer } beacon_metric_size: { type: integer } HA_send_interval: { type: integer } HA_send_changes_only: { type: boolean } RSSI_enforce_threshold: { type: boolean } RSSI_min_threshold: { type: integer } SettingsUpdate: type: object description: Partial update; only provided fields are updated additionalProperties: true Alert: type: object properties: id: { type: string } tracker_id: { type: string } type: { type: string } status: { type: string } timestamp: { type: string, format: date-time } AlertStatusUpdate: type: object required: [status] properties: status: type: string description: New status (e.g. resolved, acknowledged) ServiceStatus: type: object description: Health of an external service (e.g. Kafka, database) properties: status: type: string enum: [up, down, unknown] message: type: string description: Error detail when status is down BaseHealth: type: object properties: uptime: { type: string, description: Duration string } activeReaders: { type: array, items: { type: string } } activeWriters: { type: array, items: { type: string } } activeBeacons: { type: array, items: { type: string } } LocationHealth: allOf: - $ref: '#/components/schemas/BaseHealth' - type: object properties: activeSettings: { type: array, items: { type: object } } DecoderHealth: allOf: - $ref: '#/components/schemas/BaseHealth' BridgeHealth: allOf: - $ref: '#/components/schemas/BaseHealth' AggregatedHealth: type: object description: Health from location, decoder, bridge (via Kafka) plus server-checked Kafka and database properties: location: { $ref: '#/components/schemas/LocationHealth' } decoder: { $ref: '#/components/schemas/DecoderHealth' } bridge: { $ref: '#/components/schemas/BridgeHealth' } kafka: { $ref: '#/components/schemas/ServiceStatus' } database: { $ref: '#/components/schemas/ServiceStatus' } Track: type: object properties: id: { type: string } timestamp: { type: string, format: date-time } type: { type: string } status: { type: string } gateway: { type: string } gatewayMac: { type: string } tracker: { type: string } trackerMac: { type: string } subject: { type: string } subjectName: { type: string } floor: { type: string } signal: { type: integer } building: { type: string }