Repository navigation
Entwickler REST API
Die REST-API ist für externe Systeme (Alarmierungssystem) gedacht. Alle Endpunkte erfordern einen gültigen API-Key.
X-API-Key: ec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAPI-Keys sind org-spezifisch. Der Key wird als SHA-256-Hash gespeichert, nie im Klartext.
Jeder API-Key besitzt explizite Berechtigungen. Für die Nachrichten-API sind relevant:
| Scope | Endpunkte |
|---|---|
sms:send |
POST /api/v1/sms, POST /api/v1/sms/send, Status eigener Nachrichtenjobs |
mail:send |
POST /api/v1/mail, Status eigener Nachrichtenjobs |
Für GET /api/v1/nachricht/{id} genügt einer der beiden Scopes. Ein gültiger Key ohne den
benötigten Scope erhält HTTP 403. Bestehende Keys wurden mit
einsatz:write,mailing:import migriert und dürfen nicht automatisch Nachrichten versenden.
Alarm-Endpunkte sind per API-Key rate-limited (nicht per IP). Jeder Key hat ein eigenes Budget:
- Standard:
60/minute(konfigurierbar viaAPI_ALARM_RATELIMITin.env) - Überschreitung: HTTP 429 Too Many Requests
Der Rate-Limit-Key ist apikey:sha256(key)[:24] — verschiedene Keys beeinflussen sich nicht gegenseitig.
Legt einen neuen Einsatz an (oder gibt den bestehenden zurück bei Idempotenz).
Request:
POST /api/v1/einsatz
X-API-Key: ec_...
Content-Type: application/json{
"Key": "426747e9-0126-45bc-a0c1-b51a182de14b",
"Nummer": 1978,
"AlarmDatumZeit": "2026-05-19T21:11:11.323",
"Zeitzone": "Europe/Vienna",
"Stufe": "t3",
"Art": "T",
"Meldung": "Wolfurt Senderstraße 34 Heizraum überflutet",
"Einsatzgrund": "Heizraum überflutet",
"Ort": "Wolfurt",
"Strasse": "Senderstraße",
"HausNr": "34",
"Leitstellennummer": "fu26303655",
"Uebung": false
}Felder:
| Feld | Typ | Pflicht | Validierung | Beschreibung |
|---|---|---|---|---|
Key |
string | ja | 1–200 Zeichen, Strip, kein reines Whitespace | Idempotenz-Schlüssel |
Nummer |
integer | nein | ≥ 0 | Einsatznummer aus Alarmierungssystem |
Leitstellennummer |
string | nein | max. 40 Zeichen | Stabile Leitstellen-Einsatznummer (z. B. fu26303655). Matching-Schlüssel gegen bereits per LIS/DIBOS angelegte Einsätze (Incident.lis_operation_number) — verhindert doppelte Einsätze, wenn derselbe Alarm über mehrere Wege eintrifft. Koordinaten werden dabei nie über diesen Weg gesetzt/überschrieben — die kommen ausschließlich aus LIS/DIBOS bzw. dem eigenen Geocoding. |
AlarmDatumZeit |
ISO-8601 | nein | Zeitpunkt des Alarms | |
Zeitzone |
string (IANA) | nein | Zeitzone für naive AlarmDatumZeit
|
|
Stufe |
string | nein | max. 10 Zeichen, wird uppercase normalisiert | Alarmstufe (t1–t9, f1–f4) → F3 |
Art |
string | nein | Einsatzart: T oder F
|
|
Meldung |
string | nein | max. 5000 Zeichen | Freitext-Meldung |
Einsatzgrund |
string | nein | max. 500 Zeichen | Kurzer Grund |
Ort |
string | nein | max. 200 Zeichen | Ort/Gemeinde |
Strasse |
string | nein | max. 200 Zeichen | Straße |
HausNr |
string | nein | max. 20 Zeichen | Hausnummer |
Uebung |
boolean | nein | Übungseinsatz? (Standard: false) |
|
Name |
string | nein | max. 200 Zeichen | Meldender |
Telefon |
string | nein | max. 50 Zeichen | Rückrufnummer |
-
Mit UTC-Offset (empfohlen):
"2026-05-19T21:11:11+02:00"— wird direkt übernommen. -
Naiv (ohne Offset):
"2026-05-19T21:11:11.323"— Zeitzone-Priorität:-
Zeitzone-Feld im Request - In der Organisation hinterlegte Zeitzone
- Server-Default (
DEFAULT_TIMEZONE, Standard:Europe/Vienna)
-
Intern werden alle Zeitpunkte als UTC gespeichert.
Response (200 OK):
{
"id": 42,
"external_key": "426747e9-0126-45bc-a0c1-b51a182de14b",
"url": "/einsatz/42",
"created": true,
"board_token": "InVzZXJfaWQiOiAxfQ.abc123...",
"board_url": "https://einsatzcockpit.com/qr-login?incident_id=42&token=..."
}Bei Idempotenz (Key bereits bekannt): "created": false, "id": <vorhandene ID>.
Fehler-Responses:
| Code | Bedeutung |
|---|---|
| 401 | API-Key ungültig oder fehlt |
| 422 | Payload-Validierungsfehler (z.B. Key zu lang, Lat außerhalb Bereich) |
| 429 | Rate-Limit überschritten |
| 500 | Serverfehler |
Erstellt eine neue Einsatzstelle in einer laufenden Großschadenslage.
POST /api/v1/lage/alarm
X-API-Key: ec_...
Content-Type: application/json{
"Key": "lage-001",
"Meldung": "Wasserschaden Erdgeschoss",
"Ort": "Wolfurt",
"Strasse": "Bahnhofstraße",
"HausNr": "12",
"Lat": 47.4664,
"Lng": 9.7416
}Zusätzliche Felder gegenüber AlarmPayload:
| Feld | Typ | Validierung |
|---|---|---|
Lat |
float | -90.0 bis +90.0 |
Lng |
float | -180.0 bis +180.0 |
GET /api/v1/einsatz/active
X-API-Key: ec_...Response: Array von Einsatz-Objekten mit id, alarm_type_code, started_at, is_exercise.
GET /api/v1/einsatz/42
X-API-Key: ec_...Nimmt einen persistenten Versandauftrag an. Erforderlicher Scope: sms:send.
| Feld | Typ | Pflicht | Validierung | Beschreibung |
|---|---|---|---|---|
Key |
string | ja | 1–200 Zeichen | Idempotenz-Token je Organisation und Kanal |
text |
string | ja | 1–API_MESSAGE_MAX_BODY_CHARS Zeichen |
SMS-Inhalt |
empfaenger |
object | ja | mindestens ein gültig aufgelöstes Ziel | Empfängerauswahl |
empfaenger.nummern |
string[] | nein | strikt E.164 (+ und 8–15 Ziffern) |
freie Nummern |
empfaenger.gruppen_ids |
integer[] | nein | Gruppe muss zur Key-Organisation gehören | SMS-Gruppen |
empfaenger.mitglieder_ids |
integer[] | nein | Mitglied muss zur Key-Organisation gehören | einzelne Mitglieder |
curl -X POST https://einsatzcockpit.example.at/api/v1/sms \
-H "X-API-Key: ec_..." -H "Content-Type: application/json" \
-d '{
"Key":"sms-2026-09-01-001",
"text":"Probealarm Sirene 12:00. Keine Aktion erforderlich.",
"empfaenger":{"nummern":["+436641234567"],"gruppen_ids":[3],"mitglieder_ids":[12]}
}'Ungültige freie Nummern werden unter abgelehnt zurückgegeben und blockieren gültige Ziele nicht.
Alle Ziele werden nach normalisierter Nummer dedupliziert.
Uptime-Kuma-kompatibler Gateway-Endpunkt mit Scope sms:send. Anders als POST /api/v1/sms
wartet er auf das echte Versandergebnis, reiht keinen Hintergrundauftrag ein und arbeitet ohne
Idempotenzschutz.
| Feld | Typ | Pflicht | Validierung | Beschreibung |
|---|---|---|---|---|
to |
string | ja | komma-separiert, strikt E.164, höchstens API_SMS_SYNC_MAX_RECIPIENTS eindeutige Nummern |
SMS-Empfänger |
body |
string | ja | nicht leer, höchstens API_MESSAGE_MAX_BODY_CHARS Zeichen |
SMS-Inhalt |
curl -X POST https://einsatzcockpit.example.at/api/v1/sms/send \
-H "X-API-Key: ec_..." -H "Content-Type: application/json" \
-d '{"to":"+436641234567,+436769876543","body":"DIBOS-Poll ist ausgefallen."}'Bei vollständigem Erfolg lautet die Antwort beispielsweise:
{"status":"sent","id":42,"gesendet":2,"fehlgeschlagen":0}Ein Versandfehler wird bewusst ebenfalls mit HTTP 200, aber mit status: "failed" und einem
error-Feld gemeldet. Diese vom übrigen REST-API-Stil abweichende Semantik gilt nur für diesen
Endpunkt, weil der Uptime-Kuma-Provider das Ergebnis aus dem Body liest. Authentifizierungs-,
Validierungs-, Limit- und Konfigurationsfehler behalten ihre regulären HTTP-Codes.
Nimmt einen persistenten Mail-Auftrag an. Erforderlicher Scope: mail:send.
| Feld | Typ | Pflicht | Validierung | Beschreibung |
|---|---|---|---|---|
Key |
string | ja | 1–200 Zeichen | Idempotenz-Token je Organisation und Kanal |
betreff |
string | ja | 1–500 Zeichen | Mail-Betreff |
text |
string/null | bedingt | zusammen mit HTML höchstens API_MESSAGE_MAX_BODY_CHARS
|
Text-Version |
html |
string/null | bedingt | mindestens text oder html
|
HTML-Version |
empfaenger |
object | ja | mindestens ein gültig aufgelöstes Ziel | Empfängerauswahl |
empfaenger.adressen |
string[] | nein | plausible, Header-sichere E-Mail-Adresse | freie Adressen |
empfaenger.listen_ids |
integer[] | nein | Liste muss zur Key-Organisation gehören | Mailinglisten |
curl -X POST https://einsatzcockpit.example.at/api/v1/mail \
-H "X-API-Key: ec_..." -H "Content-Type: application/json" \
-d '{
"Key":"mail-2026-09-01-001",
"betreff":"Übungstermin verschoben",
"text":"Die Übung am Freitag entfällt.",
"html":"<p>Die Übung am Freitag entfällt.</p>",
"empfaenger":{"adressen":["max@example.at"],"listen_ids":[5]}
}'Adressen auf der Mailing-Sperrliste werden als suppressed gespeichert und nicht versendet.
Beide POST-Endpunkte antworten mit 202 Accepted:
{
"id": 41,
"kanal": "sms",
"status": "queued",
"empfaenger_anzahl": 12,
"abgelehnt": [{"wert": "0664 abc", "grund": "ungueltige_nummer"}],
"idempotent_hit": false
}Ein bereits bekannter Key liefert dieselbe id, keine neuen Empfänger und
idempotent_hit: true. Der Lookup geschieht vor der Versandwegprüfung.
Erforderlicher Scope: sms:send oder mail:send. Ein Job einer fremden Organisation liefert
404.
curl https://einsatzcockpit.example.at/api/v1/nachricht/41 \
-H "X-API-Key: ec_..."{
"id": 41,
"kanal": "sms",
"status": "partial",
"erstellt_am": "2026-09-01T10:00:00Z",
"abgeschlossen_am": "2026-09-01T10:00:14Z",
"empfaenger_anzahl": 2,
"erfolg_anzahl": 1,
"fehler_anzahl": 1,
"empfaenger": [
{"ziel":"+436641234567","name":"Max M.","status":"sent","provider":"gateway",
"gesendet_am":"2026-09-01T10:00:03Z","fehler":null}
]
}Job-Status: queued, sending, sent, partial, failed. Empfängerstatus: queued,
sending, sent, failed, suppressed. Zeitstempel sind UTC mit Z-Suffix. Mail-Empfänger
werden bis zu dreimal versucht; SMS wird wegen des Doppelversandrisikos nicht wiederholt.
| Code | Bedeutung |
|---|---|
| 401 | Key ungültig, gesperrt oder abgelaufen |
| 403 | erforderlicher Scope fehlt |
| 404 | fremde oder unbekannte Gruppe, Liste, Mitglied oder Job-ID |
| 409 | kein SMS- beziehungsweise Mail-Versandweg konfiguriert |
| 422 | Payload ungültig, zu viele oder keine gültigen Empfänger |
| 429 | Key-basiertes Request-Limit oder 24-Stunden-Empfängerlimit überschritten |
Standardmäßig gelten 20/minute, höchstens 200 Empfänger pro Auftrag, 500 SMS-Empfänger und
2.000 Mail-Empfänger je Organisation und 24 Stunden. Die Umgebungsvariablen sind in
Nachrichten-API administrieren beschrieben.
Snapshot+Delta-Kontrakt für externe/mobile Clients auf den zentralen Kontaktbestand einer
Organisation (siehe Administration: Kontaktverwaltung).
Erfordert nur einen gültigen, org-gebundenen API-Key — keinen speziellen Scope.
Für die native Android-App steht bei gleichem Antwortformat und gleichen Query-Parametern
GET /api/v1/device/kontakte/sync bereit. Dieser Endpoint authentifiziert über die bestehende
Session oder einen Device-Bearer-Token; der X-API-Key-Endpoint bleibt für Drittsysteme bestimmt.
Query-Parameter:
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
cursor |
integer | nein | Fehlt er: liefert einen Snapshot. Gesetzt: liefert ein Delta ab diesem Änderungs-Cursor. |
page_after |
integer | nein | Nur im Snapshot-Modus: Kontakt-ID, ab der weitergelesen wird (Pagination) |
limit |
integer | nein | 1–500, Standard 100 |
Snapshot-Antwort (GET /api/v1/kontakte/sync):
{
"schema_version": 1,
"mode": "snapshot",
"org_id": 1,
"cursor": 482,
"next_page": null,
"contacts": [
{
"id": 12, "typ": "person", "anzeigename": "Max Muster",
"vorname": "Max", "nachname": "Muster", "funktion": "Hausverwaltung",
"organisation": "Muster GmbH", "email": "max@example.com",
"erreichbarkeit": null, "notizen": null,
"aktiv": true, "archiviert": false, "version": 3,
"telefone": [
{"id": 5, "nummer": "0664 1234567", "label": "Mobil", "sort": 0,
"bevorzugt": true, "sms_eignung": true}
]
}
],
"mappings": [
{"id": 7, "kontakt_id": 12, "objekt_id": 3, "rolle": "hausverwaltung",
"sort": 0, "erreichbarkeit": null}
]
}mappings wird nur auf der ersten Seite (page_after=0) mitgeliefert. Ist next_page
gesetzt, gibt es weitere Kontakte — nächster Aufruf mit page_after=<next_page>.
Delta-Antwort (GET /api/v1/kontakte/sync?cursor=482):
{
"schema_version": 1,
"mode": "delta",
"org_id": 1,
"cursor": 501,
"has_more": false,
"changes": [
{"cursor": 495, "entity": "kontakt", "id": 12, "operation": "upsert", "payload": {"...": "..."}},
{"cursor": 501, "entity": "zuordnung", "id": 12, "operation": "tombstone", "payload": null}
]
}entity ist kontakt oder zuordnung, operation ist upsert oder tombstone
(Löschung/Entfernung). Bei has_more: true erneut mit cursor=<zurückgegebener cursor>
abrufen, bis has_more: false. Der Client soll den zuletzt erhaltenen cursor persistieren
und bei jedem weiteren Sync als Ausgangspunkt verwenden.
Die API normalisiert Stufe automatisch: f3 → F3, T3 bleibt T3.
# Einsatz anlegen:
curl -X POST https://einsatzcockpit.com/api/v1/einsatz \
-H "X-API-Key: ec_xxxx" \
-H "Content-Type: application/json" \
-d '{
"Key": "test-uuid-001",
"Nummer": 100,
"AlarmDatumZeit": "2026-05-22T14:30:00",
"Zeitzone": "Europe/Vienna",
"Stufe": "t1",
"Art": "T",
"Meldung": "Wasserschaden Keller",
"Ort": "Wolfurt",
"Strasse": "Teststraße",
"HausNr": "1"
}'
# Aktive Einsätze:
curl https://einsatzcockpit.com/api/v1/einsatz/active \
-H "X-API-Key: ec_xxxx"
# Rate-Limit-Header in der Response:
# X-RateLimit-Limit: 60
# X-RateLimit-Remaining: 59
# X-RateLimit-Reset: 1717000060python -m app.cli create-api-key --label "Alarmierungssystem Leitstelle" --org-id 1Ausgabe:
API-Key: ec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Key-ID: 1
Label: Alarmierungssystem Leitstelle
Den Key sofort kopieren — er wird nur einmal im Klartext angezeigt.
Einsatz führen
Im Einsatz
Einsatzbegleitend
Verwaltung & Planung
Auswertung
Großschadenslage
Verwaltung
Systemadministration
- Überblick und Inbetriebnahme
- Systemdiagnose
- Datenbank: Snapshot-Isolation
- System-Admin-MCP
- Zwei-Faktor-Authentifizierung
Mehr