Zum Hauptinhalt springen

REST API (v1)

REST API-Handbuch v1 für PlantPulse Edge. Dies ist die Standardschnittstelle für Bildschirme und externe Systeme zur Verwaltung von OPC, Tags, Monitoring und Health-Checks für externe Übertragungen.

PunktWert
Base URLhttp(s)://<edge-host>/api/v1
Content-Typeapplication/json; charset=utf-8
AuthentifizierungAPI-Key erforderlich (edge.rest.api.auth=true) — X-API-Key / Authorization: Bearer oder Passage via Login-Session. ?api_key= wird mit 401 abgelehnt
Audit-LogApiAccessLogFilter — Jeder /api/* Aufruf wird einzeilig protokolliert (Methode/Pfad/Status/Dauer/IP/Benutzer/Auth-Modus)
Hauptcontrollerplantpulse.app.edge.api.v1.* (OpcAPI, TagAPI, AppAPI, SystemAPI, MonitoringAPI)
Error Envelope{data, meta, errors} — Gilt auch für Filter/Advice-Pfade
HTTPSedge.rest.api.https_only=true Standard. Aufrufe via Plain-HTTP-Client werden abgelehnt oder folgen der Tomcat-HTTPS-Redirect-Richtlinie

Authentifizierung (ApiAuthFilter)​

Aktivierung in app.properties über edge.rest.api.auth=true. Der Schlüssel ist edge.rest.api.key und wird im Betrieb über EDGE_REST_API_KEY_FILE oder EDGE_REST_API_KEY injiziert.

Passage erfolgt, wenn mindestens eine der folgenden Bedingungen erfüllt ist:

A. Login-Session (Browser)​

Bereits über /login/form angemeldete Benutzer — Wenn ein _USER_LOGIN Attribut (JSONObject) in der Session vorhanden ist, führt ApiAuthFilter direkt chain.doFilter() aus. Alle /api/*, die via Ajax vom Bildschirm (JSP) aufgerufen werden, funktionieren ohne zusätzlichen Schlüssel.

B. X-API-Key Header (Empfohlen)​

EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "X-API-Key: %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value

C. Authorization: Bearer <key> Header​

EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value

D. ?api_key=<key> Query-Parameter — Abgelehnt​

Da Schlüssel im Klartext in der URL erscheinen und somit in Access-Logs, Proxy-Caches oder Browser-Historien verbleiben können, werden diese in v1 mit 401 abgelehnt. Bitte nutzen Sie die Header-Methoden (B/C).

Fehlerantwort​

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 401,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Invalid or missing X-API-Key."
}
]
}

Der Schlüsselvergleich erfolgt via constantTimeEquals() (Schutz gegen Timing-Attacks).

edge.rest.api.auth=false (Entwicklung / Temporär)​

Wenn die Authentifizierung deaktiviert ist, führt ApiAuthFilter sofort chain.doFilter() aus — Aufrufe von extern sind ohne Schlüssel möglich. Im produktiven Netzwerk wird grundsätzlich true empfohlen.


Audit-Log (ApiAccessLogFilter)​

Alle /api/* Aufrufe werden als einzeiliges Log aufgezeichnet — einschließlich Authentifizierungsfehlern (401), um einen Audit-Trail zu gewährleisten (Filter-Chain-Reihenfolge: ApiAccessLogFilter → ApiAuthFilter).

Format​

[API_ACCESS] method=GET path=/api/v1/tag/TAG_MS_1000/value query= status=200 duration=12ms
ip=100.127.147.98 user=admin auth=session ua=Mozilla/5.0...
FeldBedeutung
methodHTTP-Methode
pathRequest-URI (ohne Query)
queryQuery-String — Abgelehnte ?api_key=... Teile werden als *** maskiert
statusHTTP-Response-Code
durationBearbeitungszeit (ms)
ipStandard ist RemoteAddr. In Reverse-Proxy-Umgebungen mit edge.rest.api.trust_x_forwarded_for=true die erste IP X-Forwarded-For
useruser_id bei Session-Benutzer / XXXX*** (len=16) bei API-Key (erste 4 Zeichen + Länge) / - bei anonym
authsession (Browser-Login) / apikey (X-API-Key/Bearer) / deprecated_query (Abgelehrter ?api_key= Versuch) / none (Schlüssel fehlt oder anonym)
uaUser-Agent (bei mehr als 80 Zeichen gekürzt auf …)
errorBei Ausnahmen wird error=ClassName hinzugefügt

Log-Level-Aufteilung​

StatusLevelAbsicht
5xx oder ThrowableERRORServerfehler — Sofortiger Alarm
401, 403WARNAuthentifizierung/Berechtigung abgelehnt — Audit-Priorität
Sonstige (2xx, 3xx, allgemeine 4xx)INFOAlltagsaufrufe

Anwendung​

  • Wer sendet falsche Schlüssel? — Prüfung des 401-Trails via grep '\[API_ACCESS\].*status=401'
  • 5xx Regressionsverfolgung — Felder grep '\[API_ACCESS\].*status=5' + error=
  • Aufruffrequenz / Aggregation pro Benutzer — Gruppierung nach auth=apikey/session, Analyse nach IP
  • Antwortverzögerungen / Slow Queries — Extraktion langer Linien via duration=

Klartext-Schlüssel werden nirgendwo im Log gespeichert — Identifikation erfolgt nur über Query-Maskierung + User-Feld-Prefix (4 Zeichen). Dies bietet ein Gleichgewicht zwischen ausreichender Identifizierbarkeit für die Analyse von Sicherheitsvorfällen und dem Schutz vor Klartext-Exposition.

Filter-Chain-Reihenfolge ist entscheidend

Mapping-Reihenfolge in web.xml: Zuerst ApiAccessLogFilter, dann ApiAuthFilter. Da AccessLog den Status im finally-Block aufzeichnet, werden auch fehlgeschlagene 401-Aufrufe im Audit erfasst. Würde die Reihenfolge getauscht, würden 401-Aufrufe im Access-Log fehlen.


Response Envelope​

Die Antworten der v1 REST API werden in einen gemeinsamen Envelope gehüllt (RestApiSupport, ApiEnvelopeWriter).

Erfolgreiche Antwort:

{
"data": { },
"meta": {
"timestamp": 1778069486934,
"status": "OK",
"request_id": "f818abd0-eb0f-4497-9b07-7ccc94b2e2d7"
}
}

Fehlerantwort:

{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 400,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "VALIDATION_FAILED",
"message": "Validation failed"
}
]
}

Alte RPC-Style-Envelopes (result, _session_id, _user_id) werden in REST v1 nicht mehr verwendet.


1. Gemeinsame Konventionen​

Bevor Sie die Endpunkte betrachten, beachten Sie die Regeln, die für alle /api/v1/* gelten. Ohne diese Kenntnisse wird die Implementierung des Clients fehlerhaft sein, selbst wenn die einzelnen Endpunkt-Dokumentationen gelesen wurden.

1.1 Operative Header​

HeaderRichtungZweck
X-API-KeyRequestAuthentifizierung. ?api_key= Queries werden abgelehnt (401)
Authorization: Bearer <key>RequestAlternative zum API-Key
X-Request-IdRequestTracking. Wird als meta.request_id in der Antwort zurückgegeben. Falls nicht gesendet, generiert der Server einen Wert
Idempotency-KeyRequestPOST-Duplikationsschutz. TTL 10 Min. Bei erneutem Versuch mit demselben Schlüssel erfolgt die Antwort mit dem gecachten Ergebnis und Idempotency-Replay: true
If-MatchRequestOptimistisches Locking. 412, wenn es vom ETag der GET-Antwort abweicht
ETagResponseWeak Validator (W/"<hex>"). Derzeit wird nur GET /api/v1/opc/{opcId} ausgegeben
LocationResponseURI der neuen Ressource bei POST-Erstellung (201)
X-RateLimit-Limit · -Remaining · -ResetResponseRate-Limit-Informationen
Retry-AfterResponseSekunden bis zum nächsten Versuch bei 429

Das Rate-Limit wird über edge.rest.api.rate_limit.enabled und edge.rest.api.rate_limit.per_second (Standard 1000) gesteuert.

Nutzen Sie Idempotency-Key für Wiederholungsversuche

Vor-Ort-Verbindungen können unterbrochen werden. Ein POST, auf das keine Antwort folgte, ist nicht „fehlgeschlagen“, sondern „unbekannt“ — ein einfaches erneutes Senden könnte zu doppelten OPC-Einträgen führen. Senden Sie dieselbe Key-ID, und die Box gibt das erste Ergebnis erneut zurück.

1.2 Pagination​

Gemeinsame Queries für Listen-Endpunkte.

QueryStandardwertBeschreibung
page0Seitennummer, beginnend bei 0
size0Bei 0 wird die gesamte Liste zurückgegeben. Bei 1..1000 wird die Liste entsprechend gekürzt und meta.pagination gefüllt

Wenn size 1000 überschreitet oder negativ ist, oder wenn page negativ ist, erfolgt eine 400 INVALID_RANGE.

1.3 ID-Regeln​

ZielRegex
site_id^SITE_[A-Z0-9_]{1,60}$
opc_id^OPC_[A-Z0-9_]{1,60}$
tag_id^TAG_[A-Z0-9_]{1,60}$

data_type kann nur sechs Werte annehmen: Boolean · Integer · Long · Float · Double · String (entspricht Cassandra ENUM). opc_type ist eine Whitelist von Protokollkonstanten des Treibers (LS · OPCUA · MODBUS · MODBUS_RTU · HTTP · MELSEC · S7 · EIP …), die Referenzliste befindet sich in GET /v3/api-docs. Werte außerhalb der Whitelist werden als field_errors[].reason = "not_in_whitelist" abgelehnt.


2. Endpunkt-Liste​

Alle 52 Endpunkte. Die OpenAPI-Referenz befindet sich in GET /v3/api-docs der Box, die visuelle Ansicht in /public/swagger-ui/index.html.

2.1 OPC — 8​

MethodeURLBeschreibungAntwort
GET/api/v1/opcOPC-Liste + Laufzeitstatus + 60-Minuten-Trend200 · 400
POST/api/v1/opcOPC-Registrierung. Tags können via tag_list gleichzeitig hinzugefügt werden201 · 400 · 409
GET/api/v1/opc/{opcId}Details + ETag. Tag-Array via ?include=tags integriert200 · 404
PUT/api/v1/opc/{opcId}Änderung (Unterstützung für If-Match)200 · 404 · 412
PATCH/api/v1/opc/{opcId}Teilweise Änderung — nur gesendete Felder werden geändert200 · 400 · 404 · 412
DELETE/api/v1/opc/{opcId}Löschen von OPC + allen untergeordneten Tags200 · 404 · 412
POST/api/v1/opc/{opcId}/startErfassung starten200 · 404
POST/api/v1/opc/{opcId}/stopErfassung stoppen200 · 404

POST Erforderliche 7 Felder — site_id · opc_id · opc_name · opc_type · opc_agent_ip · opc_agent_port (1~65535) · timecycle (mind. 100ms). Verstöße werden gesammelt zurückgegeben (field_errors[]).

Tags werden über tag_list gleichzeitig registriert. Da normalerweise Dutzende Tags pro Verbindung registriert werden, führt eine Trennung der Registrierung zu einem Zwustand, in dem die OPC existiert, aber keine Tags vorhanden sind. Senden Sie alles in einem Aufruf — alternativ können Tags später einzeln über POST /api/v1/opc/{opcId}/tag hinzugefügt werden.

2.2 Tag — 10​

MethodeURLBeschreibungAntwort
GET/api/v1/tagGesamte Tag-Liste200 · 400
GET/api/v1/opc/{opcId}/tagTag-Liste eines einzelnen OPC200 · 400
POST/api/v1/opc/{opcId}/tagRegistrierung eines einzelnen Tags201 · 400 · 409
GET/api/v1/tag/{tagId}Tag-Details200 · 404
PUT/api/v1/tag/{tagId}Tag-Änderung200 · 404
DELETE/api/v1/tag/{tagId}Tag-Löschung200 · 404
GET/api/v1/tag/{tagId}/valueLetzter Wert. Bei ?fresh=true wird der Wert neu vom Gerät gelesen200 · 404 · 503
POST/api/v1/tag/{tagId}/valueWert schreiben + anschließendes erneutes Lesen (actual_read)200 · 400 · 404 · 503
POST/api/v1/tag-values:readCache-Werte mehrerer Tags gleichzeitig (max. 100)200 · 400
GET/api/v1/tag/{tagId}/pointsZeitreihe abfragen + Downsampling200 · 400 · 500

POST Erforderliche 6 Felder — site_id · opc_id (automatisch aus Pfad) · tag_id · tag_name · data_type · plc_address.

Query für points: from·to (Epoch ms, from < to) · minutes (Standard 10, 11440) · limit (Standard 600, 15000 — bei Überschreitung wird intervallbasiert ausgedünnt).

Code-Mapping bei Schreibfehlern — Tag fehlt 404 TAG_NOT_FOUND · Verbindung unterbrochen 503 DISCONNECTED · Erfassung gestoppt 503 OPC_STOPPED · Schreibgeschützter Tag 400 RW_VIOLATION · Typkonflikt 400 TYPE_MISMATCH · Sonstige 500 INTERNAL_ERROR.

2.3 App (Docker Container) — 9​

MethodeURLBeschreibungAntwort
GET/api/v1/appContainer-Liste200 · 400
POST/api/v1/appRun nach Image-Pull201 · 400 · 409 · 502
GET/api/v1/app/{name}Ergebnis von docker inspect200 · 404
DELETE/api/v1/app/{name}Löschen (inkl. Image bei ?image=true)200 · 404
POST/api/v1/app/{name}/startStarten200 · 404
POST/api/v1/app/{name}/stopStoppen200 · 404
POST/api/v1/app/{name}/restartNeustarten200 · 404
GET/api/v1/app/{name}/logs?lines=NLetzte N Zeilen (1~5000, Standard 200)200 · 404
GET/api/v1/app/{name}/statsCPU · Memory · Network · Disk Snapshot200 · 404

name und image müssen Regex-Prüfungen und die Sperre von Shell-Metazeichen passieren (; & | \ $ ( ) < > \ " ' Leerzeichen). memory_limit_mb는 16 이상,cpu_limit은 0.1 이상,network는bridge·host·none` .

Bereitstellungsfehler geben die Stufe an — Pull-Fehler 502 DOCKER_PULL_FAILED, Run-Fehler 502 DOCKER_RUN_FAILED, existierender Name 409 CONTAINER_EXISTS.

2.4 System — 12​

MethodeURLBeschreibungAuthentifizierung
GET/api/v1/system/healthLiveness + Prüfung von 8 KomponentenNicht erforderlich
GET/api/v1/system/readyReadiness (Monitor + V5 Client)Nicht erforderlich
GET/api/v1/system/versionVersion · Build-Datum · Image-Tag · Container-StatusNicht erforderlich
GET/api/v1/system/timeAktuelle Zeit der Box (Epoch ms)Erforderlich
POST/api/v1/system/time/syncZeit an Plattform angleichen ({"platform_time":…})Erforderlich
GET/api/v1/system/logsAktuelle Logs als JSONErforderlich
GET/api/v1/system/logs/downloadLog-Verzeichnis via tar.gzErforderlich
GET/api/v1/system/eventsSSE-Stream (text/event-stream)Erforderlich
GET/api/v1/system/upgrade/checkVergleich mit neuester Version des Download-ServersErforderlich
POST/api/v1/system/upgradeUpgrade-TriggerErforderlich
GET/api/v1/system/upgrade/statusUpgrade-Fortschritt (Polling)Erforderlich
POST/api/v1/system/firmwareFirmware-Update-TriggerErforderlich
POST/api/v1/system/rebootHost-Reboot nach 5 SekundenErzwungen
POST/api/v1/system/restartEdge-Agent-Neustart nach 3 SekundenErzwungen
Authentifizierung für reboot · restart kann «nicht deaktiviert» werden

Selbst wenn die Authentifizierung über edge.rest.api.auth=false deaktiviert wurde, fordern diese beiden Endpunkte weiterhin eine Session-Anmeldung oder einen API-Key. Die Offenheit dieser Funktionen würde an sich zu einem Denial-of-Service führen.

2.5 Monitoring · Edge — 4​

MethodeURLBeschreibung
GET/api/v1/monitoringUptime · Anzahl OPCs · registrierte Tags · Scan-Geschwindigkeit
GET/api/v1/edgeedge_id · site_id · Version · Hostname · Modus
GET/api/v1/edge/statusEdge-Status-Zusammenfassung
GET/api/v1/transferStatus pro ausgehender Übertragung (MQTT · SparkplugB · Plattform-API)

monitoring gibt unmittelbar nach dem Booten, solange kein Monitor vorhanden ist, einen 503 MONITOR_NOT_READY aus — dies ist kein Fehler, sondern ein Status «noch nicht bereit».

2.6 Redundanz (HA) — 4​

MethodeURLBeschreibung
GET/api/v1/ha/statusSchreibrechte dieser Edge · Modus (manual/auto) · Startzeitpunkt · Status der Gegenpartner-Box
PUT/api/v1/ha/write-authoritySchreibrechte dieser Edge erhöhen oder senken ({"enabled":true})
PUT/api/v1/ha/failover/overrideManueller Eingriff in automatisches Failover — nur im auto Modus (sonst 409 NOT_AUTO_MODE)
POST/api/v1/ha/definitionsTag-Definitionen an Partner replizieren

Schreibrechte — Grundwissen vor dem Lesen​

Was wird blockiert? Eine Edge ohne Berechtigung lehnt reale PLC-Schreibvorgänge ab (WRITE_NOT_AUTHORIZED). Der Konfigurationsschlüssel ist edge.plc.write_authority; falls nicht vorhanden, gilt true — eine frisch installierte Box startet mit Berechtigung.

HTTP Erfassung durchläuft dieses Gate nicht — jedoch nicht «immer»

Verbindungen, bei denen opc_type auf HTTP gesetzt ist, stellen keine Schreibvorgänge an Geräte dar, sondern eine eingehende Erfassung. Daher wird die HTTP-Erfassung auch an einer Edge mit gesenkten Rechten fortgesetzt, da keine Gefahr eines Doppel-Schreibzugriffs besteht.

⚠ Unmittelbar nach dem Booten kann auch dieses HTTP-Schreiben kurzzeitig abgelehnt werden. Das Gate liest opc_type aus dem Cache. Wenn der Cache noch nicht gefüllt ist oder die Abfrage fehlschlägt, wird es als «reale PLC» eingestuft und blockiert (Sicherheitsmodus). Dieser Abfragefehler wird nicht protokolliert.

Sollte das Verhalten «HTTP sollte funktionieren, wurde aber abgelehnt» auftreten, prüfen Sie zuerst, ob der Startvorgang gerade erfolgt ist. Sobald der Cache gefüllt ist, löst sich das Problem von selbst — es ist keine Konfigurationsänderung erforderlich.

🔴 Diese API «verschiebt» nicht — es sind zwei unabhängige Aufrufe zum Erhöhen und Senken

Dieser Aufruf ändert nur die eigene Box. Er fragt den Partner nicht ab, sperrt ihn nicht und übernimmt keine Rechte. Es gibt keinen Koordinationscode.

Daher ist ein Zustand, in dem beide Boxen gleichzeitig Berechtigungen besitzen, tatsächlich möglich. Halten Sie die Reihenfolge ein:

  1. A senken — PUT /api/v1/ha/write-authority {"enabled":false}
  2. Bestätigen, dass A auf false steht — GET /api/v1/ha/status
  3. B erhöhen

Bei falscher Reihenfolge entsteht ein Zeitfenster, in dem beide true sind und gleichzeitig auf dieselbe PLC schreiben.

GET /api/v1/ha/status zeigt via peer.write_authority zwar den Wert des Partners an, dies ist jedoch eine Beobachtung, kein Zwang — der Aufruf wird nicht blockiert, selbst wenn der Partner die Berechtigung hat.

persisted:false ist kein «Fehler», sondern bedeutet «verschwindet nach Neustart»

Das Toggle erfolgt in zwei Schritten. ① Live-Übernahme — das Gate sieht den neuen Wert sofort (kein Neustart erforderlich). ② app.properties Persistenz — selbst wenn dies fehlschlägt, bleibt ① aktiv und wird als persisted:false gemeldet.

Das bedeutet: Es funktioniert jetzt, kehrt aber beim nächsten Neustart zum alten Wert zurück. Dies ist die häufigste Fehlerquelle vor Ort, wenn man glaubt, die Berechtigung gesenkt zu haben, diese aber nach dem Neustart wieder aktiv ist. Lösen Sie die Ursache vor dem Neustart — meist handelt es sich um die Schreibberechtigung von /etc/kopens/conf. Die Diagnose findet sich nicht in der Antwort, sondern im Server-Log:

[HA] write-authority persist to app.properties failed — applied live only

since ist der «Zeitpunkt der letzten erfolgreichen Steuerungsübergabe». Dieser Wert wird nur aktualisiert, wenn sich der Wert tatsächlich ändert; beim Senden desselben Wertes erfolgt keine Aktualisierung.

Audits werden immer erstellt — bei Änderung WARN, bei Unverändertheit INFO mit Anhäng «[no-op]», beide enthalten IP und Konto des Aufrufers.

[HA][AUDIT] write-authority false -> true by 10.0.0.5 (admin)

⚠ Übergabehistorien und Plattform-Diagnose-Pushes erfolgen nur bei Änderung. Wenn diese fehlschlagen, bleibt das Toggle dennoch intakt (nur Log-Eintrag). «Fehlt in der Historie» bedeutet nicht zwingend «nicht geändert» — Prüfen Sie den Status via status.

Zwei Zustände, die die Edge selbst meldet. Die überwachende Seite protokolliert abweichende Zustände. Die Konfiguration wird nicht automatisch korrigiert — dies muss der Operator tun.

[HA] DUAL WRITE AUTHORITY detected — both edges have edge.plc.write_authority=true.
[HA] NO WRITE AUTHORITY — both edges have edge.plc.write_authority=false.

Der hintere Zustand bedeutet, dass überall PLC-Schreibvorgänge blockiert werden — tritt auf, wenn «beide gesenkt, aber vergessen, eine wieder zu erhöhen».

Manuelles Failover-Intervent — PUT /api/v1/ha/failover/override​

Das automatische Failover stoppt die Entscheidung für diese Edge und fixiert die Rolle. Die automatische Entscheidung setzt erst nach Aufhebung wieder ein.

roleBedeutung
"WRITER"Heartbeat senden und Berechtigung halten
"STANDBY"Schreiben stoppen und Berechtigung freigeben
nullIntervention aufheben — Rückkehr zur automatischen Entscheidung
{ "data": { "override": "WRITER", "previous": null }, "meta": { "status": "OK" } }
  • Nur im auto Modus verfügbar. Wenn kein automatisches Failover läuft, erfolgt eine 409 NOT_AUTO_MODE — da write-authority in beiden Modi funktioniert, nutzen Sie diesen bei manuellem Betrieb.
  • WRITER·STANDBY sind case-insensitive.
  • Wenn der Wert nicht einem der drei entspricht, erfolgt eine 400 VALIDATION_FAILED mit field_errors:[{field:"role", reason:"invalid_value"}].

Zeitsynchronisation — POST /api/v1/system/time/sync​

{ "platform_time": 1789780169674 }

Epoch-Millisekunden (positiv), akzeptiert Zahlen oder Zahlen-Strings. Ohne platform_time erfolgt die Synchronisation über die in der Box konfigurierte NTP-Quelle — das Senden eines Wertes und das Weglassen sind unterschiedliche Operationen.

Die Antwort erfolgt im Format {"applied":true,"method":"system","before":…,"after":…}. Ein Vergleich von before/after zeigt, wie stark die Zeit tatsächlich abgewichen war.

Authentifizierung erfolgt «vor der Validierung»

Da es sich um einen systemverändernden Aufruf handelt, wird zuerst die Authentifizierung geprüft. Ohne Schlüssel oder Session erfolgt 401; ist die Authentifizierung korrekt, aber der Body fehlerhaft, folgt 400 VALIDATION_FAILED. Verwechseln Sie dies nicht mit APIs, bei denen die Reihenfolge umgekehrt ist — ein 401 bei fehlerhaftem Body liegt nicht am Body.

2.7 Backup · Recovery — 2​

MethodeURLBeschreibung
POST/api/v1/connect/backupAlle OPC + alle Tags in einem JSON
POST/api/v1/connect/restoreDieses JSON anwenden (Upsert)

Symmetrisch — die Antwort von Backup kann direkt als Body für Restore gesendet werden. Prüfen Sie unbedingt skipped[] in der Restore-Antwort: Selbst wenn einzelne Zeilen fehlschlagen, schlägt nicht der gesamte Prozess fehl, sondern Zeilen werden übersprungen und der Zähler reduziert.

2.8 Formel — 1​

MethodeURLBeschreibung
POST/api/v1/tags/formula/previewErgebnis einer Formel vor der Speicherung prüfen — Formel

2.9 Nicht-authentifizierte Schnittstellen​

Nur dieses Netz durchläuft ApiAuthFilter. Es gibt keine OPC- oder Tag-Informationen aus.

GET /api/v1/system/health · GET /api/v1/system/ready · GET /api/v1/system/version · GET /api/health (Alias ohne Version)

«Keine Authentifizierung» bedeutet, dass Schlüssel nicht verifiziert werden

Dieses Netz gibt 200 zurück, selbst wenn X-API-Key fehlt oder falsch ist. Daher eignet es sich nicht, um zu prüfen, ob ein Schlüssel korrekt geändert wurde — nutzen Sie hierfür Pfade, die Schlüssel erfordern, wie GET /api/v1/opc.

/health prüft acht Felder. Wenn alle acht UP sind, erfolgt status=UP + 200, ansonsten status=DEGRADED + 503.

{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP", "node_red": "UP",
"opc_ua": "UP", "edge_core": "UP", "tse": "UP", "grafana": "UP"
}
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}

🔴 Auch bei 503 wird components unverändert übertragen. Dies ist die einzige Diagnosemöglichkeit — wer den Body aufgrund des Statuscodes verwirft, kann nicht feststellen, welches der acht Felder ausgefallen ist.

Die folgenden drei Felder werden separat erläutert, um Missverständnisse zu vermeiden.

FeldTatsächliche Prüfung
edge_coreEs handelt sich um readiness — ob Collector/Monitor gestartet sind. Während eines Boot-Race ist dies DOWN, weshalb Health 503 ist. Es ist unabhängig von der Plattform-Verbindung und daher auch für STANDALONE-Boxen relevant
opc_uaDer Port ist nicht fix. Er folgt opc.ua.server.tcp.port von app.properties (Dev-Box 4840, Standard 12000). Da früher 12000 hardcodiert war, wurden Dev-Boxen fälschlicherweise als dauerhaft DOWN gemeldet
grafanaAb Image 2026-07 ist dies HTTPS. Bei Abfrage via Plain-TCP hinterlässt Grafana alle 10 Sekunden TLS handshake error … EOF im Log — daher nutzt die Box zuerst eine Probe, die den Handshake vollständig durchführt, und hält die TCP-Prüfung als Fallback für alte Stacks bereit

cassandra(9042) · redis(6379) · mqtt(1883) · node_red(1880) · tse(7800) sind TCP-Prüfungen auf fixen Ports.

Beispiel für /version:

{
"data": {
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "20260523",
"image_tag": "2026-20260523",
"container_mode": true
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}

OTA upgrade.sh prüft /api/health über 300 Sekunden, um einen automatischen Rollback zu entscheiden; Docker HEALTHCHECK nutzt ebenfalls diesen Pfad.

2.10 Nicht-API-Endpunkte — /ui/opcua/* · /config/*​

Es gibt Pfade, die nur vom Frontend genutzt werden. Neue Clients sollen diese nicht verwenden.

  • /ui/opcua/info · /tree · /history — Nur für die integrierte OPC UA Server-Oberfläche. Die offizielle v1 API des Produkts hat diese als obsolet erklärt. Der Code antwortet noch, ist aber kein verbindlicher Vertrag.
  • /config/load · /save · /restart · /reboot · /upgrade · /firmware · /backup · /temp-clean · /platform/test — Pfade der Administrationsseite. Nutzen Sie stattdessen /api/v1/system/*, das die gleiche Funktion erfüllt und dem v1-Standard für Envelopes und Fehlercodes folgt.
  • /api/http(APIController) — Obsolet. Nutzen Sie POST /api/v1/tag/{tagId}/value.

3. Request / Response Beispiele​

Alle folgenden Beispiele verwenden den aktuellen Envelope. Beispielhafte Formen wie {"result":"OK", ...} aus alten Dokumenten wurden entfernt, da sie nicht mehr mit dem Produkt übereinstimmen — Clients, die so implementiert wurden, funktionieren nicht mehr.

3.1 OPC-Registrierung​

curl -sk -X POST "https://<엣지>/api/v1/opc" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"site_id": "SITE_00001",
"opc_id": "OPC_LS_XBM_0001",
"opc_name": "1라인 LS XBM",
"opc_type": "LS",
"opc_agent_ip": "192.168.0.10",
"opc_agent_port": 2004,
"timecycle": 1000,
"tag_list": [
{ "site_id": "SITE_00001", "opc_id": "OPC_LS_XBM_0001",
"tag_id": "TAG_LS_XBM_0001", "tag_name": "1호기 토출압력",
"data_type": "Float", "plc_address": "%MW100" }
]
}'

tag_list kann weggelassen werden — dann wird nur die OPC erstellt.

201 + Location: /api/v1/opc/OPC_LS_XBM_0001:

{
"data": { "info": { "opc_id": "OPC_LS_XBM_0001", "opc_name": "1라인 LS XBM" } },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
opc_agent_port und timecycle können als Zahlen gesendet werden

Da die entsprechenden Spalten im Speicher Strings sind, konvertiert der Server diese. Es ist normal, Werte als Zahlen zu senden und als Strings zu empfangen.

3.2 Löschung​

{
"data": { "deleted": true, "opc_id": "OPC_X" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}

DELETE antwortet ebenfalls mit 200 + Body. Auch wenn in Swagger-Kommentaren 204 steht, ist das tatsächliche Verhalten 200 — in v1 gibt jede Methode einen Envelope zurück.

3.3 Fehlerantworten​

POST /api/v1/opc/OPC_NONE/start — 404:

{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "OPC_NOT_FOUND", "message": "OPC OPC_NONE 가 존재하지 않습니다." } ]
}

data fehlt komplett (das Feld null wird bei der Serialisierung weggelassen). Unterscheiden Sie Erfolg und Fehler nicht an der Präsenz von data, sondern am HTTP-Status oder an meta.status.

3.4 Mehrere Tag-Werte gleichzeitig​

curl -sk -X POST "https://<엣지>/api/v1/tag-values:read" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-d '{ "tag_ids": ["TAG_LS_XBM_0001", "TAG_LS_XBM_0002"] }'
{
"data": [
{ "tag_id": "TAG_LS_XBM_0001", "value": "1234", "timestamp": "2026-05-15T11:32:45.000Z", "quality": "SUCCESS" },
{ "tag_id": "TAG_LS_XBM_0002", "value": null, "timestamp": null, "quality": "PENDING" }
],
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}

🔴 Beachten Sie quality. PENDING bedeutet nicht «Wert ist 0», sondern «noch nie gelesen». Wenn dies als 0 interpretiert wird, wird eine nie befragte Anlage als «Normal» gemeldet.

Leere Arrays oder mehr als 100 Einträge führen zu 400(MISSING_FIELD / TOO_MANY).

3.5 Neustart​

{
"data": { "message": "reboot scheduled in 5 seconds" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}

Status 202 — bedeutet nicht «erledigt», sondern «wird erledigt». Die Box ist zum Zeitpunkt der Antwort noch aktiv.

3.6 Noch nicht bereit​

GET /api/v1/system/ready — 503:

{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "NOT_READY", "message": "엣지의 일부 구성요소가 준비되지 않았습니다." } ]
}

3.7 Symmetrie von Backup und Restore​

{
"data": {
"opcs": [ { "opc_id": "OPC_LS_XBM_0001", "opc_name": "..." } ],
"tags": [ { "tag_id": "TAG_LS_XBM_0001", "opc_id": "OPC_LS_XBM_0001" } ],
"count": { "opc": 1, "tag": 1 },
"exported_at": 1778925572946
},
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}

Senden Sie data einfach als Body für POST /api/v1/connect/restore. Prüfen Sie unbedingt skipped[] in der Restore-Antwort — selbst wenn einzelne Zeilen fehlschlagen, bleibt der Status 200, und nur die übersprungenen Einträge werden dort gelistet. Wer nur auf count achtet, übersieht eine unvollständige Wiederherstellung.


4. Fehlercodes​

🔴 v1 kommuniziert Fehler über den HTTP-Status. Die alte RPC-Methode (Verpackung von {result:"ERROR"} in HTTP 200) wurde eingestellt. Clients, die den Statuscode ignorieren und im Body nach result suchen, interpretieren jeden Fehler als Erfolg.

Die Entscheidung erfolgt über entweder den HTTP-Statuscode oder meta.status ("OK" / "ERROR"). Es ist nicht nötig, nach errors[] zu suchen; eine einzige Key-Prüfung genügt.

CodeHTTPBedeutung
MISSING_BODY400Request-Body ist leer
MISSING_FIELD400Erforderliche Felder fehlen
INVALID_JSON400JSON-Parsing fehlgeschlagen
INVALID_RANGE400Bereichsverletzung page·size·from~to
INVALID_ARGUMENT400Allgemeiner Argumentenfehler (Image/Volume Whitelist etc.)
INVALID_NAME400Injection-Zeichen im Containernamen
VALIDATION_FAILED400Mehrere Felder gleichzeitig fehlerhaft — siehe field_errors[]
TOO_MANY400Batch-Limit (100 Einträge) überschritten
RW_VIOLATION · TYPE_MISMATCH400Tag-Schreibfehler — schreibgeschützter Tag / Typkonflikt
UNAUTHORIZED401Authentifizierung fehlt oder ungültig
HTTPS_REQUIRED403Aufruf via Plain-HTTP erfolgt — kein Schlüsselproblem
OPC_NOT_FOUND · TAG_NOT_FOUND · APP_NOT_FOUND404Ziel nicht gefunden
CONTAINER_EXISTS409Container mit diesem Namen existiert bereits
PRECONDITION_FAILED412ETag von If-Match weicht ab — Änderung durch Dritte
START_FAILED · STOP_FAILED422OPC-Erfassungssteuerung fehlgeschlagen
DOCKER_PULL_FAILED · DOCKER_RUN_FAILED502Docker-Befehl fehlgeschlagen
MONITOR_NOT_READY · NOT_READY · DISCONNECTED · OPC_STOPPED503Noch nicht bereit / Unterbrochen — Request ist nicht fehlerhaft
MISCONFIGURED500edge.rest.api.key ist leer
INTERNAL_ERROR · *_FAILED500Sonstige interne Fehler
Behandeln Sie 503 nicht wie 400

DISCONNECTED · OPC_STOPPED · MONITOR_NOT_READY liegen nicht an einem fehlerhaften Request, sondern an einer aktuellen Unmöglichkeit der Ausführung. Derselbe Request sollte später erneut gesendet werden. Eine Behandlung wie 400 («Korrigieren und erneut senden») führt zu unnötigen Änderungsversuchen, obwohl kein Fehler im Request vorliegt.


5. Funktionsnotizen​

  • Einzelabfrage Tag: R03 von APP_TAG.xml (WHERE TAG_ID = :tag_id ALLOW FILTERING).
  • Nach OPC-Registrierung · Tag-Registrierung wird ConnectService.restartComponents() zurückgerufen, um den Cache neu zu lesen und den OPC-UA Server neu zu starten — dies führt zu einer kurzzeitigen Unterbrechung der Erfassung unmittelbar nach der Registrierung.
  • Wert schreiben (tagWriteByTagId): HTTP via HTTPDriver.bind(), andere via driver.write(ProtocolAddress). Bei Fehlern wird ein hilfreicher Hinweis (ok=false, z.B. EIP-Firmware-Kompatibilität) beigefügt. Das Ergebnis eines sofortigen Read-After-Write wird in actual_read zurückgegeben (außer bei HTTP).
  • Transfer Health (/api/v1/transfer): Sammelt getStatus() aller in TransferRegistry registrierten Outbound-Komponenten. Die Sparkplug B Card (/ui/main) ruft dies alle 5 Sekunden auf.
  • OPC UA Server (/ui/opcua/*) nutzt internes Polling (2s) — Verwendung nur von OPCAndTagsCache.getInstance() + LastValueMap.getInstance() zur Lastminimierung.
  • Legacy Deprecated: /api/http (APIController) — Verwendung von /api/v1/tag/{tagId}/value empfohlen.

6. Code-Positionen​

  • Controller: src/plantpulse/app/edge/api/v1/ — OpcAPI · TagAPI · AppAPI · SystemAPI · SystemTimeAPI · MonitoringAPI · EdgeStatusAPI · HaAPI · ConnectIoAPI · FormulaAPI
  • MCP-Server: src/plantpulse/app/edge/module/mcp/ — MCP-Server
  • Envelope · Gemeinsame Antworten: api/v1/RestApiSupport.java · api/v1/dto/Api*.java
  • OPC UA Server: src/plantpulse/app/edge/module/opcua/OPCUAViewerController.java
  • Upgrade-Proxy: src/plantpulse/app/edge/module/system/config/ConfigController.java
  • Business-Logik: src/plantpulse/app/edge/module/connect/ConnectService.java
  • Response-Envelope-Helper: plantpulse.app.edge.core.web.ControllerSupport (ok() / error())