# API Anforderungen für Alarmierungen

**Version:** 1.2
**Stand:** März 2026
**Herausgeber:** Dexa Consult GmbH
**Produkt:** Safe Fire House (SFH)

---

## 1. Übersicht

Diese Spezifikation definiert die REST-API-Schnittstelle für die Übermittlung von Alarmdaten von der Safe Fire House Brandwarnanlage an externe Alarmierungsdienste. Die API ermöglicht sowohl die Erstmeldung eines Alarms als auch nachfolgende Updates bei Broadcast-Alarmen.

### 1.1 Alarmtypen

| Typ | Beschreibung | Methode |
|-----|--------------|---------|
| `ALARM` | Lokaler Rauchalarm – Einzelner Rauchsensor hat ausgelöst | POST |
| `ALARM` (Broadcast) | Folge-Alarm – Weitere Rauchsensoren in Funkreichweite | PUT |
| `TEST` | Schnittstellen-Test zur Validierung der Verbindung | POST |

---

## 2. API-Parameter

| Parameter | Wert |
|-----------|------|
| **Base-URL** | `https://{partner-domain}/api/v1` |
| **Content-Type** | `application/json; charset=UTF-8` |
| **Accept** | `application/json` |
| **Zeichenkodierung** | UTF-8 |

### 2.1 Authentifizierung

Die Authentifizierung erfolgt via **Bearer Token** im HTTP-Header:

```http
Authorization: Bearer {access_token}
```

| Parameter | Beschreibung |
|-----------|--------------|
| `access_token` | Vom Partner bereitgestellter API-Schlüssel (min. 32 Zeichen) |

**Hinweis:** Der Token wird pro Kunde/Standort vom Partner generiert und im SFH-System hinterlegt.

### 2.2 Rate Limiting

| Parameter | Wert |
|-----------|------|
| Max. Requests | 60 pro Minute |
| Retry-After | Bei HTTP 429 im Header angegeben |

---

## 3. Erstalarm (POST)

Sendet einen neuen Alarm an das Partner-System. Der Partner legt einen neuen Alarm-Datensatz an und gibt eine eindeutige `alarmId` zurück.

### 3.1 Request

```http
POST /api/v1/alarms HTTP/1.1
Host: {partner-domain}
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json
```

### 3.2 Request-Body

```json
{
  "externalCreatedAt": "2026-03-13T15:30:00Z",
  "externalId": "SFH-20260313-153000-001",
  "keyword": "ALARM",
  "keywordAddition": "RAUCHSENSOR-ALARM",
  "info": "Rauchsensor hat durch lokale Raucherkennung ausgelöst!",
  "priority": false,
  "send_push": true,
  "send_sms": false,
  "send_call": false,
  "group": "FW-Musterstadt-Zug1",
  "destination": {
    "objectName": "Feuerwehrgerätehaus Musterstadt",
    "info": "Fahrzeughalle",
    "street": "Hauptstraße",
    "houseNumber": "112",
    "zipCode": "12345",
    "city": "Musterstadt",
    "coordinates": {
      "latitude": 51.123456,
      "longitude": 7.654321
    },
    "fireAlarmSystem": "Safe Fire House"
  },
  "publisherInfos": {
    "systemName": "DXO-SFH-CU-X-02",
    "version": "2.0"
  },
  "reporter": [
    {
      "name": "HLF20-Kabine",
      "info": "Raucherkennung"
    }
  ]
}
```

### 3.3 Request-Felder

#### Root-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `externalCreatedAt` | string | ✓ | Zeitstempel der Alarmerstellung (ISO 8601, UTC) |
| `externalId` | string | ✓ | Eindeutige Alarm-ID aus dem SFH-System (für Idempotenz) |
| `keyword` | string | ✓ | Alarmtyp: `ALARM` oder `TEST` |
| `keywordAddition` | string | ✓ | Detailbeschreibung: `RAUCHSENSOR-ALARM`, `SCHNITTSTELLEN-TEST` |
| `info` | string | ✓ | Freitext-Information zum Alarm |
| `priority` | boolean | ✓ | Prioritäts-Flag (reserviert für zukünftige Nutzung) |
| `send_push` | boolean | ✓ | Push-Benachrichtigung senden |
| `send_sms` | boolean | ✓ | SMS-Benachrichtigung senden |
| `send_call` | boolean | ✓ | Telefonanruf auslösen |
| `group` | string | ✓ | Alarmierungsgruppe/RIC beim Partner |
| `destination` | object | ✓ | Standort-Objekt (siehe unten) |
| `publisherInfos` | object | ✓ | System-Informationen (siehe unten) |
| `reporter` | array | ✓ | Array von Rauchsensor-Objekten (siehe unten) |

#### destination-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `objectName` | string | ✓ | Name des Gebäudes/Objekts |
| `info` | string | | Zusatzinformation zum Standort |
| `street` | string | ✓ | Straßenname |
| `houseNumber` | string | ✓ | Hausnummer |
| `zipCode` | string | ✓ | Postleitzahl |
| `city` | string | ✓ | Stadt/Ort |
| `coordinates` | object | ✓ | Koordinaten-Objekt mit `latitude` und `longitude` |
| `fireAlarmSystem` | string | ✓ | Systemkennung, immer `Safe Fire House` |

#### coordinates-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `latitude` | number | ✓ | Breitengrad (WGS84, Dezimalgrad) |
| `longitude` | number | ✓ | Längengrad (WGS84, Dezimalgrad) |

#### publisherInfos-Objekt

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `systemName` | string | ✓ | Produktkennung, z.B. `DXO-SFH-CU-X-02` |
| `version` | string | ✓ | Produktversion: `1.0` oder `2.0` |

#### reporter-Objekt (Array-Element)

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `name` | string | ✓ | Gerätename/OPTA des auslösenden Rauchsensors |
| `info` | string | ✓ | Art der Erkennung, z.B. `Raucherkennung` |

### 3.4 Response (Erfolg)

```http
HTTP/1.1 201 Created
Content-Type: application/json
```

```json
{
  "status": "created",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:01Z"
}
```

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `status` | string | `created` bei erfolgreichem Anlegen |
| `alarmId` | string | **Eindeutige ID des angelegten Alarms** (UUID oder PK) – wird für PUT benötigt! |
| `received` | string | Zeitstempel der Verarbeitung beim Partner (ISO 8601, UTC) |

---

## 4. Alarm-Update (PUT)

Aktualisiert einen bestehenden Alarm (z.B. bei Broadcast-Alarm, wenn weitere Rauchsensoren auslösen). Die `alarmId` aus der POST-Response wird im URL-Pfad übergeben.

### 4.1 Request

```http
PUT /api/v1/alarms/{alarmId} HTTP/1.1
Host: {partner-domain}
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json
```

**URL-Parameter:**

| Parameter | Beschreibung |
|-----------|--------------|
| `alarmId` | Die vom Partner beim POST zurückgegebene Alarm-ID |

### 4.2 Request-Body

```json
{
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "SFH-20260313-153000-001",
  "externalUpdatedAt": "2026-03-13T15:30:10Z",
  "keyword": "ALARM",
  "keywordAddition": "RAUCHSENSOR-ALARM (BROADCAST)",
  "info": "Weitere Rauchsensoren haben durch Broadcast-Alarm ausgelöst!",
  "priority": false,
  "send_push": true,
  "send_sms": false,
  "send_call": false,
  "group": "FW-Musterstadt-Zug1",
  "destination": {
    "objectName": "Feuerwehrgerätehaus Musterstadt",
    "info": "Fahrzeughalle",
    "street": "Hauptstraße",
    "houseNumber": "112",
    "zipCode": "12345",
    "city": "Musterstadt",
    "coordinates": {
      "latitude": 51.123456,
      "longitude": 7.654321
    },
    "fireAlarmSystem": "Safe Fire House"
  },
  "publisherInfos": {
    "systemName": "DXO-SFH-CU-X-02",
    "version": "2.0"
  },
  "reporter": [
    {
      "name": "HLF20-Kabine",
      "info": "Raucherkennung"
    },
    {
      "name": "HLF20-Mannschaftsraum",
      "info": "Raucherkennung (Broadcast)"
    }
  ]
}
```

### 4.3 Unterschiede zum POST

| Feld | POST | PUT |
|------|------|-----|
| `alarmId` | Nicht vorhanden | ✓ Pflicht (im Body UND URL) |
| `externalCreatedAt` | ✓ | Nicht vorhanden |
| `externalUpdatedAt` | Nicht vorhanden | ✓ Pflicht |
| `keywordAddition` | `RAUCHSENSOR-ALARM` | `RAUCHSENSOR-ALARM (BROADCAST)` |
| `reporter` | 1 Rauchsensor | 1+ Rauchsensoren (kumulativ) |

### 4.4 Response (Erfolg)

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "status": "updated",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:11Z"
}
```

---

## 5. Alarm-Status abrufen (GET)

Ruft den aktuellen Status eines Alarms ab, einschließlich der Rückmeldungen der alarmierten Einsatzkräfte.

### 5.1 Request

```http
GET /api/v1/alarms/{alarmId} HTTP/1.1
Host: {partner-domain}
Authorization: Bearer {access_token}
Accept: application/json
```

**URL-Parameter:**

| Parameter | Beschreibung |
|-----------|--------------|
| `alarmId` | Die vom Partner beim POST zurückgegebene Alarm-ID |

### 5.2 Response (Erfolg)

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "status": "ok",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "SFH-20260313-153000-001",
  "alarmStatus": "active",
  "createdAt": "2026-03-13T15:30:01Z",
  "updatedAt": "2026-03-13T15:30:11Z",
  "feedback": {
    "total": 12,
    "responses": [
      {
        "type": "coming",
        "label": "Komme",
        "count": 7
      },
      {
        "type": "coming_delayed",
        "label": "Komme später",
        "count": 2,
        "details": [
          { "eta": 5, "count": 1 },
          { "eta": 10, "count": 1 }
        ]
      },
      {
        "type": "not_available",
        "label": "Nicht verfügbar",
        "count": 3
      }
    ],
    "pending": 5
  },
  "received": "2026-03-13T15:31:00Z"
}
```

### 5.3 Response-Felder

#### Root-Objekt

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `status` | string | `ok` bei erfolgreicher Abfrage |
| `alarmId` | string | Partner-interne Alarm-ID (UUID/PK) |
| `externalId` | string | Ursprüngliche ID aus dem SFH-System (für Abgleich) |
| `alarmStatus` | string | Aktueller Alarmstatus (siehe Enum) |
| `createdAt` | string | Zeitpunkt der Alarmerstellung beim Partner (ISO 8601) |
| `updatedAt` | string | Zeitpunkt der letzten Aktualisierung (ISO 8601) |
| `feedback` | object | Rückmeldungs-Objekt (siehe unten) |
| `received` | string | Zeitstempel dieser Abfrage (ISO 8601) |

#### alarmStatus Enum

| Wert | Beschreibung |
|------|--------------|
| `active` | Alarm ist aktiv, Alarmierung läuft |
| `acknowledged` | Alarm wurde quittiert |
| `closed` | Alarm wurde abgeschlossen |
| `cancelled` | Alarm wurde storniert |

#### feedback-Objekt

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `total` | integer | Gesamtzahl der alarmierten Einsatzkräfte |
| `responses` | array | Array von Rückmelde-Objekten (siehe unten) |
| `pending` | integer | Anzahl noch ausstehender Rückmeldungen |

#### responses-Objekt (Array-Element)

| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|--------------|
| `type` | string | ✓ | Maschinenlesbarer Rückmeldetyp (siehe Enum) |
| `label` | string | ✓ | Menschenlesbarer Text (Sprache des Partners) |
| `count` | integer | ✓ | Anzahl der Rückmeldungen dieses Typs |
| `details` | array | | Optional: Detailierte Aufschlüsselung (z.B. ETA-Zeiten) |

#### response.type Enum (Standardisiert)

| Typ | Beschreibung |
|-----|--------------|
| `coming` | Kommt zum Einsatz |
| `coming_delayed` | Kommt später (mit ETA) |
| `not_available` | Nicht verfügbar / Kommt nicht |
| `standby` | Bereitschaft / Evtl. verfügbar |
| `on_scene` | Bereits vor Ort |
| `unknown` | Sonstiger/Unbekannter Status |

#### details-Objekt (Optional, für coming_delayed)

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `eta` | integer | Geschätzte Ankunftszeit in Minuten |
| `count` | integer | Anzahl mit dieser ETA |

### 5.4 Beispiel: Status-Abfrage

```bash
curl -X GET "https://partner.example.com/api/v1/alarms/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Accept: application/json"
```

**Response:**
```json
{
  "status": "ok",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "SFH-20260313-153000-001",
  "alarmStatus": "active",
  "createdAt": "2026-03-13T15:30:01Z",
  "updatedAt": "2026-03-13T15:30:11Z",
  "feedback": {
    "total": 12,
    "responses": [
      { "type": "coming", "label": "Komme", "count": 7 },
      { "type": "coming_delayed", "label": "Komme später", "count": 2, "details": [{ "eta": 5, "count": 1 }, { "eta": 10, "count": 1 }] },
      { "type": "not_available", "label": "Nicht verfügbar", "count": 3 }
    ],
    "pending": 5
  },
  "received": "2026-03-13T15:31:00Z"
}
```

---

## 6. Fehlerbehandlung

### 6.1 HTTP-Statuscodes

| Code | Bedeutung | Beschreibung |
|------|-----------|--------------|
| `200` | OK | Alarm erfolgreich aktualisiert (PUT) |
| `201` | Created | Alarm erfolgreich angelegt (POST) |
| `400` | Bad Request | Ungültiger Request-Body oder fehlende Pflichtfelder |
| `401` | Unauthorized | Fehlender oder ungültiger Bearer Token |
| `403` | Forbidden | Token gültig, aber keine Berechtigung für diese Ressource |
| `404` | Not Found | Alarm-ID nicht gefunden (bei PUT) |
| `409` | Conflict | Alarm mit dieser `externalId` existiert bereits (bei POST) |
| `429` | Too Many Requests | Rate Limit überschritten |
| `500` | Internal Server Error | Serverfehler beim Partner |
| `503` | Service Unavailable | Partner-System temporär nicht verfügbar |

### 6.2 Fehler-Response

```json
{
  "status": "error",
  "error": "invalid_payload",
  "message": "Field 'externalId' is required",
  "received": "2026-03-13T15:30:01Z"
}
```

| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `status` | string | Immer `error` |
| `error` | string | Fehlercode (siehe unten) |
| `message` | string | Menschenlesbare Fehlerbeschreibung |
| `received` | string | Zeitstempel der Fehlerverarbeitung |

### 6.3 Fehlercodes

| Code | Beschreibung |
|------|--------------|
| `invalid_payload` | JSON-Syntax ungültig oder Pflichtfeld fehlt |
| `invalid_field` | Feldwert entspricht nicht dem erwarteten Format |
| `unauthorized` | Token fehlt oder ist ungültig |
| `forbidden` | Keine Berechtigung für diese Operation |
| `not_found` | Ressource (Alarm) nicht gefunden |
| `duplicate` | Alarm mit dieser `externalId` existiert bereits |
| `rate_limited` | Zu viele Anfragen |
| `internal_error` | Interner Serverfehler |

---

## 7. Idempotenz & Retry-Verhalten

### 7.1 Idempotenz

Das Feld `externalId` dient der Idempotenz-Sicherung:

- Bei wiederholtem POST mit gleicher `externalId` sollte der Partner **HTTP 409 Conflict** zurückgeben
- Alternativ kann der Partner ein Upsert-Verhalten implementieren (Update statt Insert)

### 7.2 Retry-Strategie (SFH-seitig)

| Fehlertyp | Retry | Wartezeit |
|-----------|-------|-----------|
| Netzwerkfehler | Ja | 5s, 10s, 30s |
| HTTP 5xx | Ja | 5s, 10s, 30s |
| HTTP 429 | Ja | Retry-After Header beachten |
| HTTP 4xx (außer 429) | Nein | – |

---

## 8. Sicherheitsanforderungen

| Anforderung | Beschreibung |
|-------------|--------------|
| **Transport** | Ausschließlich HTTPS (TLS 1.2+) |
| **Token-Speicherung** | Access Token verschlüsselt auf der SFH-Zentrale |
| **Token-Rotation** | Empfohlen: Jährliche Erneuerung |
| **IP-Whitelisting** | Optional: Partner kann SFH-IPs whitelisten |

---

## 9. Beispiel: Vollständiger Alarm-Flow

### 9.1 Schritt 1: Lokaler Alarm (POST)

Ein Rauchsensor im HLF20 löst aus:

```bash
curl -X POST "https://partner.example.com/api/v1/alarms" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "externalCreatedAt": "2026-03-13T15:30:00Z",
    "externalId": "SFH-20260313-153000-001",
    "keyword": "ALARM",
    "keywordAddition": "RAUCHSENSOR-ALARM",
    "info": "Rauchsensor hat durch lokale Raucherkennung ausgelöst!",
    "priority": false,
    "send_push": true,
    "send_sms": false,
    "send_call": false,
    "group": "FW-Musterstadt-Zug1",
    "destination": {
      "objectName": "Feuerwehrgerätehaus Musterstadt",
      "info": "Fahrzeughalle",
      "street": "Hauptstraße",
      "houseNumber": "112",
      "zipCode": "12345",
      "city": "Musterstadt",
      "coordinates": { "latitude": 51.123456, "longitude": 7.654321 },
      "fireAlarmSystem": "Safe Fire House"
    },
    "publisherInfos": { "systemName": "DXO-SFH-CU-X-02", "version": "2.0" },
    "reporter": [{ "name": "HLF20-Kabine", "info": "Raucherkennung" }]
  }'
```

**Response:**
```json
{
  "status": "created",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:01Z"
}
```

### 9.2 Schritt 2: Broadcast-Alarm (PUT)

~10 Sekunden später lösen weitere Rauchsensoren im Fahrzeug aus:

```bash
curl -X PUT "https://partner.example.com/api/v1/alarms/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "alarmId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "SFH-20260313-153000-001",
    "externalUpdatedAt": "2026-03-13T15:30:10Z",
    "keyword": "ALARM",
    "keywordAddition": "RAUCHSENSOR-ALARM (BROADCAST)",
    "info": "Weitere Rauchsensoren haben durch Broadcast-Alarm ausgelöst!",
    "priority": false,
    "send_push": true,
    "send_sms": false,
    "send_call": false,
    "group": "FW-Musterstadt-Zug1",
    "destination": {
      "objectName": "Feuerwehrgerätehaus Musterstadt",
      "info": "Fahrzeughalle",
      "street": "Hauptstraße",
      "houseNumber": "112",
      "zipCode": "12345",
      "city": "Musterstadt",
      "coordinates": { "latitude": 51.123456, "longitude": 7.654321 },
      "fireAlarmSystem": "Safe Fire House"
    },
    "publisherInfos": { "systemName": "DXO-SFH-CU-X-02", "version": "2.0" },
    "reporter": [
      { "name": "HLF20-Kabine", "info": "Raucherkennung" },
      { "name": "HLF20-Mannschaftsraum", "info": "Raucherkennung (Broadcast)" }
    ]
  }'
```

**Response:**
```json
{
  "status": "updated",
  "alarmId": "550e8400-e29b-41d4-a716-446655440000",
  "received": "2026-03-13T15:30:11Z"
}
```

---

## Anhang A: Constraints

| Feld | Constraint |
|------|------------|
| `externalId` | Max. 50 Zeichen, Pattern: `[A-Za-z0-9\-]+` |
| `keyword` | Enum: `ALARM`, `TEST` |
| `keywordAddition` | Max. 50 Zeichen |
| `info` | Max. 500 Zeichen |
| `group` | Max. 100 Zeichen |
| `destination.street` | Max. 100 Zeichen |
| `destination.houseNumber` | Max. 10 Zeichen |
| `destination.zipCode` | 5 Zeichen (DE) |
| `destination.city` | Max. 100 Zeichen |
| `coordinates.latitude` | -90.0 bis 90.0 |
| `coordinates.longitude` | -180.0 bis 180.0 |
| `reporter[].name` | Max. 50 Zeichen |
| `reporter` Array | Min. 1 Element |

---