API Reference

All PLC-scoped endpoints use the prefix /api/plcs/{plcId}/ where plcId is the configured alias (e.g., Line1, MyPLC).

Response Envelope

Every response uses a standard envelope:

// Success
{ "data": { ... }, "error": null }

// Error
{ "data": null, "error": { "code": "ADS_TIMEOUT", "message": "..." } }

Error Codes

Code

HTTP Status

Description

SYMBOL_NOT_FOUND

404

PLC symbol does not exist

PLC_NOT_FOUND

404

PLC alias not configured

PLC_UNAVAILABLE

503

PLC is not connected

ADS_TIMEOUT

504

ADS operation timed out

ADS_ERROR

502

Generic ADS protocol error

TYPE_MISMATCH

400

Value type doesn’t match what’s expected — e.g. a non-scalar (object/array) RPC parameter, see RPC

BATCH_LIMIT_EXCEEDED

400

Batch request exceeds 100 symbols

SUBSCRIPTION_LIMIT_EXCEEDED

400

Exceeded max symbols per client

RATE_LIMITED

429

Lifecycle rate limit hit

FEATURE_DISABLED

404 (501 for Alarms, Rpc, TypeMetadata)

Feature slice is disabled — most slices answer 404; those three answer 501 so a fleet-aggregating gateway can tell “genuinely absent” from “transiently failing”

UNAUTHORIZED

401

Missing or invalid JWT

FORBIDDEN

403

Insufficient role or scope

PLC_ACCESS_DENIED

403

plc_access claim denies this PLC

MASTER_NOT_FOUND

404

EtherCAT master ID not found

SLAVE_NOT_FOUND

404

EtherCAT slave address not found

NOT_IMPLEMENTED

501

Endpoint not implemented by the underlying ADS library (all /files routes)

Symbol Path Format

TwinCAT dot notation: MAIN.nCounter, MAIN.stMotor.bEnabled, GVL.arr[0]

Array indices with brackets must be URL-encoded in path segments: MAIN.arr%5B0%5D

System Endpoints

GET  /api/plcs                   List all PLCs + connection status
GET  /api/plcs/{plcId}/status    Detailed status for one PLC
GET  /health/live                Liveness probe
GET  /health/ready               Readiness probe (PLC connectivity)
GET  /health/alarms              Worst outstanding alarm severity (when Alarms is enabled)
GET  /openapi/v1.json            OpenAPI spec
GET  /swagger                    Scalar API UI (Development only)

Health probes

Endpoint: Liveness Probe API_HEALTH_LIVE
status: implemented
component: Infrastructure
method: GET
route: /health/live
policy: Anonymous
implements: FEAT_HEALTH

Answers 200 Healthy for as long as the process runs. It consults no PLC, so it stays a plain-text body.

Endpoint: Readiness Probe API_HEALTH_READY
status: implemented
component: Infrastructure
method: GET
route: /health/ready
policy: Anonymous
implements: FEAT_HEALTH

Runs the checks that say whether adsify can serve what it was configured to serve (plc-connectivity, plus ethercat-diagnostics when enabled) and answers JSON naming each configured PLC.

The /health/* routes are the only anonymous surface adsify exposes — /api/plcs and /api/plcs/{plcId}/status both require a token, which a load balancer or uptime checker generally does not hold.

/health/live answers 200 Healthy for as long as the process runs. It consults no PLC, so it stays a plain-text body.

/health/ready runs the checks that say whether adsify can serve what it was configured to serve — plc-connectivity, plus ethercat-diagnostics when that feature is enabled — and answers JSON naming each configured PLC:

{
  "status": "Degraded",
  "checks": [
    {
      "name": "plc-connectivity",
      "status": "Degraded",
      "description": "1/2 PLCs connected",
      "data": { "Line1": "Connected", "Line2": "Disconnected" }
    }
  ]
}

Condition

Status

HTTP

All configured PLCs connected

Healthy

200

Some connected, some not

Degraded

200

No PLCs connected, or none configured

Unhealthy

503

An EtherCAT master’s diagnostics are unreadable

Degraded

200

When EtherCAT diagnostics are enabled, a second check reports whether adsify can still read each master — a different question from whether the PLC is connected, and the two come apart exactly when it matters: an ADS connection can be healthy while the master behind it has been unreadable for minutes.

{
  "name": "ethercat-diagnostics",
  "status": "Degraded",
  "description": "1/2 EtherCAT masters have unreadable diagnostics",
  "data": { "Line1/1": "Ok", "Line1/2": "DiagnosticsDegraded" }
}

A master adsify cannot read at all reports DiagnosticsDegraded and degrades the check. One it read but not completely reports IncompleteReads: … and does not — a host whose frame-counter index group is unsupported reports frameStatistics incomplete on every cycle for the life of the deployment, and that is not a fault. See EtherCAT for both signals.

A partial outage answers 200, deliberately. A host serving several targets can still serve the ones that are up, so failing the probe over one unreachable line would pull the working PLCs out of rotation too. Alert on the body — data names the PLC — rather than on the status code.

/health/ready never reflects alarm severity. See Alarms for /health/alarms and why the two are separate.