Öffentliche OpenAPI-3.1-Spezifikation der eingehenden Push-Schnittstelle.
openapi: 3.1.0
info:
title: IGNIS2AB Event Push API
version: 1.0.0
description: |
Herstellerneutraler, mandantengetrennter HTTP-Push-Empfang für Leitstellen,
Alarmierungsdienste und andere sendende Systeme.
Ein Bearer-Token ist genau einer Organisation und einer Feuerwehr zugeordnet.
Jede Änderung wird mit einer stabilen event_id erneut zustellbar und idempotent verarbeitet.
servers:
- url: https://app.ignis2ab.com
description: Production environment
- url: https://demo.ignis2ab.com
description: Demo environment
- url: https://dev.ignis2ab.com
description: Development and integration environment
security:
- bearerAuth: []
paths:
/api/v1/connect/health:
get:
operationId: eventPushHealth
summary: Validate bearer token and assigned organization
responses:
'200':
description: Connection is ready
content:
application/json:
schema:
type: object
required: [status, integration, organization_id, site_id]
properties:
status: {type: string, const: ok}
integration: {type: string, const: event-push}
organization_id: {type: string, format: uuid}
site_id: {type: string, format: uuid}
'401': {$ref: '#/components/responses/Unauthorized'}
'403': {$ref: '#/components/responses/Forbidden'}
/api/v1/connect/events:
post:
operationId: pushEvent
summary: Push one ordered mission, resource, or feedback change
description: |
Send exactly one event for one logical change. mission.created carries the initial
mission snapshot. Later events contain only changed mission fields and only the
affected resources or feedback. Resource and feedback changes use their dedicated
event types and are not embedded in mission events.
Repeating the same event_id with identical content succeeds as duplicate. Reusing it
with changed content returns 409. The first event is mission.created with sequence 1;
every later event must use exactly the preceding sequence plus one. A sender receiving
sequence_gap must retry the missing predecessor before retrying the rejected event.
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/Event'}
examples:
missionCreated:
value:
event_id: adc-event-01892357
event_type: mission.created
occurred_at: '2026-08-12T14:31:22+02:00'
sequence: 1
mission:
id: adc-mission-4711
control_center_incident_number: LS-MSH-2026-004711
status: active
category: fire
keyword: B2 Gebäude
description: Rauchentwicklung aus Wohngebäude
alarmed_at: '2026-08-12T14:27:03+02:00'
started_at: null
location:
name: Wohnhaus
street: Beispielstraße
house_number: '12'
postal_code: '06542'
city: Allstedt
district: Beyernaumburg
latitude: 51.4734
longitude: 11.3572
transport_destination: null
missionUpdate:
value:
event_id: adc-event-01892358
event_type: mission.updated
occurred_at: '2026-08-12T14:35:00+02:00'
sequence: 2
mission:
id: adc-mission-4711
description: Rauchentwicklung bestätigt, Personen nicht mehr im Gebäude
responses:
'200':
description: Event processed or already processed as an identical duplicate
content:
application/json:
schema: {$ref: '#/components/schemas/Accepted'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'403': {$ref: '#/components/responses/Forbidden'}
'409':
description: event_id conflict, sequence conflict, or a missing predecessor
'415':
description: Content-Type must be application/json
'422':
description: Required fields are missing or invalid
/api/v1/connect/appointments:
post:
operationId: pushAppointment
summary: Create or update an appointment and its participants
description: |
Transmits dates, exercises, service evenings or meetings together with the
invited people and their current response. Each change has a stable change_id
and a monotonically increasing revision per appointment. Repeating identical
content with the same change_id is a successful duplicate; reusing the ID with
different content returns 409. Older revisions are ignored. This is a complete
appointment snapshot: participants replaces the prior list and [] clears it.
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/Appointment'}
examples:
serviceEvening:
value:
appointment_id: adc-appointment-2026-0815
change_id: adc-appointment-change-0042
revision: 3
title: Dienstabend Technische Hilfeleistung
description: Ausbildung am HLF
location: Gerätehaus Beyernaumburg
starts_at: '2026-08-15T18:00:00+02:00'
ends_at: '2026-08-15T20:00:00+02:00'
status: scheduled
participants:
- user_id: adc-user-88421
status: accepted
responded_at: '2026-08-13T10:12:00+02:00'
- user_id: adc-user-99117
status: maybe
responded_at: '2026-08-13T10:14:00+02:00'
responses:
'200':
description: Appointment processed, ignored as stale, or already processed as an identical duplicate
content:
application/json:
schema: {$ref: '#/components/schemas/AppointmentAccepted'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'403': {$ref: '#/components/responses/Forbidden'}
'409': {description: change_id or revision was already used for different content}
'422': {description: Required fields are missing or invalid}
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: organization token
responses:
Unauthorized: {description: "Bearer token missing, invalid or expired"}
Forbidden: {description: "Token has no enabled event-push connection"}
BadRequest: {description: "Malformed JSON request"}
schemas:
Event:
type: object
additionalProperties: false
required: [event_id, event_type, occurred_at, sequence, mission]
properties:
event_id: {type: string, minLength: 1, maxLength: 180, description: Stable unique delivery/event identifier}
event_type:
type: string
enum: [mission.created, mission.updated, mission.ended, mission.cancelled, resource.updated, feedback.updated]
occurred_at: {type: string, format: date-time}
sequence:
type: integer
minimum: 1
description: Contiguous order per mission. Starts at 1 and increments by exactly one; gaps and conflicting repetitions return 409
mission: {$ref: '#/components/schemas/Mission'}
resources:
type: array
minItems: 1
description: Resource changes for this event; each id may occur only once
items: {$ref: '#/components/schemas/Resource'}
feedback:
type: array
minItems: 1
description: Response or assignment changes for this event; each user_id may occur only once
items: {$ref: '#/components/schemas/Feedback'}
allOf:
- if: {properties: {event_type: {const: resource.updated}}}
then:
required: [resources]
not: {required: [feedback]}
properties:
mission: {$ref: '#/components/schemas/MissionReference'}
- if: {properties: {event_type: {const: feedback.updated}}}
then:
required: [feedback]
not: {required: [resources]}
properties:
mission: {$ref: '#/components/schemas/MissionReference'}
- if: {properties: {event_type: {const: mission.created}}}
then:
not:
anyOf: [{required: [resources]}, {required: [feedback]}]
- if: {properties: {event_type: {const: mission.updated}}}
then:
not:
anyOf: [{required: [resources]}, {required: [feedback]}]
properties:
mission:
allOf:
- {$ref: '#/components/schemas/Mission'}
- not:
properties: {status: {enum: [ended, cancelled]}}
required: [status]
- if: {properties: {event_type: {const: mission.ended}}}
then:
not:
anyOf: [{required: [resources]}, {required: [feedback]}]
properties:
mission:
$ref: '#/components/schemas/EndedMission'
- if: {properties: {event_type: {const: mission.cancelled}}}
then:
not:
anyOf: [{required: [resources]}, {required: [feedback]}]
properties:
mission:
$ref: '#/components/schemas/CancelledMission'
MissionReference:
type: object
additionalProperties: false
required: [id]
properties:
id: {type: string, minLength: 1, maxLength: 180}
EndedMission:
type: object
additionalProperties: false
required: [id]
properties:
id: {type: string, minLength: 1, maxLength: 180}
status: {type: string, const: ended, description: Optional; event_type is authoritative}
ended_at: {type: [string, 'null'], format: date-time}
CancelledMission:
type: object
additionalProperties: false
required: [id]
properties:
id: {type: string, minLength: 1, maxLength: 180}
status: {type: string, const: cancelled, description: Optional; event_type is authoritative}
ended_at: {type: [string, 'null'], format: date-time}
Mission:
type: object
additionalProperties: false
required: [id]
properties:
id: {type: string, minLength: 1, maxLength: 180, description: Stable mission ID assigned by the sending system}
control_center_incident_number: {type: [string, 'null'], maxLength: 120}
status: {type: string, enum: [alarmed, active, ended, cancelled]}
category: {type: [string, 'null']}
keyword: {type: [string, 'null'], maxLength: 180}
description: {type: [string, 'null']}
alarmed_at: {type: [string, 'null'], format: date-time}
started_at: {type: [string, 'null'], format: date-time}
ended_at: {type: [string, 'null'], format: date-time}
transport_destination: {type: [string, 'null']}
location:
type: object
additionalProperties: false
properties:
name: {type: [string, 'null']}
street: {type: [string, 'null']}
house_number: {type: [string, 'null']}
postal_code: {type: [string, 'null']}
city: {type: [string, 'null']}
district: {type: [string, 'null']}
latitude: {type: [number, 'null'], minimum: -90, maximum: 90}
longitude: {type: [number, 'null'], minimum: -180, maximum: 180}
Resource:
type: object
additionalProperties: false
required: [id]
properties:
id: {type: string, minLength: 1, maxLength: 180, description: Stable resource identity; journey_number does not change the identity}
removed: {type: boolean, default: false, description: Remove an existing internal vehicle assignment for this mission}
radio_call_sign: {type: [string, 'null'], maxLength: 180}
journey_number: {type: [string, 'null'], maxLength: 120}
status: {type: [string, 'null'], description: 'German BOS status, e.g. D, A, 1, 2, 3, 4, 7, 8'}
status_at: {type: [string, 'null'], format: date-time}
vehicle_id: {type: [string, 'null'], format: uuid, description: Preferred internal IGNIS2AB vehicle ID}
registration_plate: {type: [string, 'null'], maxLength: 30, description: Fallback used to match a vehicle in the token-bound fire department}
alarmed_at: {type: [string, 'null'], format: date-time}
started_at: {type: [string, 'null'], format: date-time, description: Vehicle departure time}
arrived_at: {type: [string, 'null'], format: date-time}
ended_at: {type: [string, 'null'], format: date-time, description: Scene departure and return time when no separate times are supplied}
crew_count: {type: [integer, 'null'], minimum: 0}
Feedback:
type: object
additionalProperties: false
required: [user_id, status]
properties:
user_id: {type: string, minLength: 1, maxLength: 180, description: Stable user ID assigned by the sending system and unique identity within the event}
removed: {type: boolean, default: false, description: Remove an existing internal personnel assignment; a non-accepted status has the same assignment effect}
status: {type: string, enum: [accepted, declined, maybe, no_response, unknown]}
responded_at: {type: [string, 'null'], format: date-time}
person_id: {type: [string, 'null'], format: uuid, description: Preferred internal IGNIS2AB person ID}
personnel_number: {type: [string, 'null'], description: Fallback used to match personnel in the token-bound fire department}
alarmed_at: {type: [string, 'null'], format: date-time}
started_at: {type: [string, 'null'], format: date-time}
ended_at: {type: [string, 'null'], format: date-time}
incident_function:
type: [string, 'null']
enum: [incident_commander, platoon_leader, group_leader, squad_leader, machine_operator, driver, breathing_apparatus, safety_assistant, radio_operator, messenger, crew, other, null]
task: {type: [string, 'null'], maxLength: 180}
Appointment:
type: object
additionalProperties: false
required: [appointment_id, change_id, revision, title, starts_at, participants]
properties:
appointment_id: {type: string, minLength: 1, maxLength: 180}
change_id: {type: string, minLength: 1, maxLength: 180, description: Stable unique identity of this appointment change within the connection}
revision: {type: integer, minimum: 1, description: Monotonically increasing per appointment}
title: {type: string, minLength: 1, maxLength: 180}
description: {type: [string, 'null']}
location: {type: [string, 'null'], maxLength: 180}
starts_at: {type: string, format: date-time}
ends_at: {type: [string, 'null'], format: date-time}
status: {type: string, enum: [scheduled, cancelled, completed]}
participants:
type: array
uniqueItems: true
description: Complete replacement list; user_id must occur at most once and an empty array clears the list
items: {$ref: '#/components/schemas/Feedback'}
Accepted:
type: object
additionalProperties: false
required: [event_id, status, duplicate]
properties:
event_id: {type: string}
status: {type: string, const: processed}
duplicate: {type: boolean}
incident_id: {type: string, format: uuid}
resources: {type: integer, minimum: 0, description: Resource entries stored from this event}
assigned_personnel: {type: integer, minimum: 0, description: Internal personnel assignments created or updated}
unmatched_personnel: {type: integer, minimum: 0, description: Accepted feedback entries that could not be mapped to personnel}
assigned_vehicles: {type: integer, minimum: 0, description: Internal vehicle assignments created or updated}
unmatched_vehicles: {type: integer, minimum: 0, description: Vehicle resource entries that could not be mapped}
feedback: {type: integer, minimum: 0, description: Feedback entries stored from this event}
unmatched_feedback: {type: integer, minimum: 0, description: Feedback entries that could not be mapped to personnel}
AppointmentAccepted:
type: object
additionalProperties: false
required: [appointment_id, change_id, status, duplicate]
properties:
appointment_id: {type: string}
change_id: {type: string}
status: {type: string, enum: [processed, ignored]}
duplicate: {type: boolean}
stale: {type: boolean}
matched_participants: {type: integer, minimum: 0}
unmatched_participants: {type: integer, minimum: 0}