REST API Reference

The ros2_medkit gateway exposes a RESTful API for interacting with ROS 2 systems. All endpoints are prefixed with /api/v1.

Note

Entity endpoints (/components, /apps) share the same handler implementations. The examples use /components but the same patterns apply to /apps.

Server Capabilities

GET /api/v1/

Get server capabilities and entry points.

Example Response:

{
  "name": "ROS 2 Medkit Gateway",
  "version": "0.4.0",
  "api_base": "/api/v1",
  "endpoints": [
    "GET /api/v1/health",
    "GET /api/v1/areas",
    "GET /api/v1/components",
    "GET /api/v1/apps",
    "GET /api/v1/functions",
    "GET /api/v1/faults",
    "..."
  ],
  "capabilities": {
    "discovery": true,
    "data_access": true,
    "operations": true,
    "async_actions": true,
    "configurations": true,
    "faults": true,
    "logs": true,
    "bulk_data": true,
    "cyclic_subscriptions": true,
    "triggers": true,
    "updates": false,
    "authentication": false,
    "tls": false
  }
}
GET /api/v1/version-info

Get gateway version and status information.

Example Response:

{
  "items": [
    {
      "version": "1.0.0",
      "base_uri": "/api/v1",
      "vendor_info": {
        "version": "0.4.0",
        "name": "ros2_medkit"
      }
    }
  ]
}
GET /api/v1/health

Health check endpoint. Returns HTTP 200 if gateway is operational.

Discovery Endpoints

Areas

GET /api/v1/areas

List all areas (logical/physical groupings).

Example Response:

{
  "items": [
    {
      "id": "powertrain",
      "name": "Powertrain",
      "href": "/api/v1/areas/powertrain"
    }
  ]
}
GET /api/v1/areas/{area_id}

Get area capabilities and metadata.

GET /api/v1/areas/{area_id}/contains

List components contained in this area.

GET /api/v1/areas/{area_id}/components

List components in a specific area.

Note

ros2_medkit extension: Areas support resource collections beyond the SOVD spec, which only defines them for apps and components. Areas provide /data, /operations, /configurations, /faults, /logs (namespace prefix aggregation), read-only /bulk-data, and /triggers. See SOVD Compliance for details.

Components

GET /api/v1/components

List all components with their operations and capabilities.

Example Response:

{
  "items": [
    {
      "id": "temp_sensor",
      "name": "temp_sensor",
      "href": "/api/v1/components/temp_sensor"
    }
  ]
}
GET /api/v1/components/{component_id}

Get component capabilities including available resource collections.

GET /api/v1/components/{component_id}/hosts

List apps hosted on this component (SOVD 7.6.2.4).

GET /api/v1/components/{component_id}/depends-on

List component dependencies.

Apps

GET /api/v1/apps

List all apps discovered by the gateway.

The set of apps is populated either from the static manifest (manifest or hybrid mode) or via heuristic runtime discovery of ROS 2 nodes (see Heuristic Runtime Discovery). This endpoint may return an empty list if no apps are discovered or if app discovery is disabled in the gateway configuration.

GET /api/v1/apps/{app_id}

Get capabilities for a single discovered app.

GET /api/v1/apps/{app_id}/is-located-on

Return the parent component that hosts this app.

The response follows the standard items wrapper and returns:

  • 0 items when the app has no associated host component

  • 1 item when the host component is resolved

  • 1 item with x-medkit.missing=true when the app references a host component that cannot currently be resolved

Example Response:

{
  "items": [
    {
      "id": "temp-sensor-hw",
      "name": "Temperature Sensor",
      "href": "/api/v1/components/temp-sensor-hw"
    }
  ],
  "x-medkit": {
    "total_count": 1
  },
  "_links": {
    "self": "/api/v1/apps/engine-temp-sensor/is-located-on",
    "app": "/api/v1/apps/engine-temp-sensor"
  }
}

Unknown apps return 404 App not found with parameters.app_id.

Functions

GET /api/v1/functions

List all functions (requires manifest mode or hybrid mode).

GET /api/v1/functions/{function_id}

Get function capabilities.

GET /api/v1/functions/{function_id}/hosts

List apps that host this function.

GET /api/v1/functions/{function_id}/x-medkit-graph

Get a function-scoped topology snapshot with per-topic metrics and pipeline status.

Example Response:

{
  "x-medkit-graph": {
    "schema_version": "1.0.0",
    "graph_id": "perception_graph-graph",
    "timestamp": "2026-03-08T12:00:00.000Z",
    "scope": {
      "type": "function",
      "entity_id": "perception_graph"
    },
    "pipeline_status": "degraded",
    "bottleneck_edge": "edge-2",
    "topics": [
      {
        "topic_id": "topic-1",
        "name": "/camera/front/image_raw"
      }
    ],
    "nodes": [
      {
        "entity_id": "camera_front",
        "node_status": "reachable"
      },
      {
        "entity_id": "detector",
        "node_status": "unreachable",
        "last_seen": "2026-03-08T11:59:42.100Z"
      }
    ],
    "edges": [
      {
        "edge_id": "edge-2",
        "source": "camera_front",
        "target": "detector",
        "topic_id": "topic-1",
        "transport_type": "unknown",
        "metrics": {
          "source": "greenwave_monitor",
          "frequency_hz": 12.5,
          "latency_ms": 4.2,
          "drop_rate_percent": 0.0,
          "metrics_status": "active"
        }
      }
    ]
  }
}

Field Notes:

  • pipeline_status: overall graph state, one of healthy, degraded, broken

  • node_status: per-node reachability, one of reachable, unreachable

  • metrics_status: per-edge telemetry state, one of pending, active, error

  • error_reason: present when metrics_status is error; one of node_offline, topic_stale, no_data_source

Note

ros2_medkit extension: Functions support resource collections beyond the SOVD spec. /data and /operations aggregate from hosted apps (per SOVD). Additionally, /configurations, /faults, /logs aggregate from hosts, read-only /bulk-data is available, /cyclic-subscriptions and /triggers are supported, and the vendor resource /x-medkit-graph exposes a function-scoped graph snapshot. See SOVD Compliance for details.

Data Endpoints

Read and publish data from ROS 2 topics.

GET /api/v1/components/{id}/data

Read all topic data from an entity.

Example Response:

{
  "items": [
    {
      "name": "temperature",
      "data_id": "powertrain%2Fengine%2Ftemperature",
      "type": "std_msgs/msg/Float64",
      "value": {"data": 85.5},
      "timestamp": "2025-01-15T10:30:00Z"
    }
  ],
  "x-medkit": {
    "entity_id": "temp_sensor",
    "total_count": 1
  }
}
GET /api/v1/components/{id}/data/{topic_path}

Read specific topic data. Topic path is URL-encoded (/%2F).

Example:

curl http://localhost:8080/api/v1/components/temp_sensor/data/powertrain%2Fengine%2Ftemperature
PUT /api/v1/components/{id}/data/{topic_path}

Publish to a topic.

  • Content-Type: application/json

  • 200: Message published successfully

  • 400: Invalid message format

  • 401: Unauthorized (when auth enabled)

Example:

curl -X PUT http://localhost:8080/api/v1/components/brake_actuator/data/chassis%2Fbrakes%2Fcommand \
  -H "Content-Type: application/json" \
  -d '{"data": 50.0}'

Operations Endpoints

Execute ROS 2 services and actions.

List Operations

GET /api/v1/components/{id}/operations

List all operations (services and actions) for an entity.

Example Response:

{
  "items": [
    {
      "id": "calibrate",
      "name": "calibrate",
      "type": "service",
      "service_type": "std_srvs/srv/Trigger",
      "schema": {
        "request": {},
        "response": {"success": "bool", "message": "string"}
      }
    },
    {
      "id": "long_calibration",
      "name": "long_calibration",
      "type": "action",
      "action_type": "example_interfaces/action/Fibonacci",
      "schema": {
        "goal": {"order": "int32"},
        "result": {"sequence": "int32[]"},
        "feedback": {"partial_sequence": "int32[]"}
      }
    }
  ],
  "x-medkit": {
    "entity_id": "calibration",
    "total_count": 2
  }
}
GET /api/v1/components/{id}/operations/{operation_id}

Get operation details and schema.

Execute Operations

POST /api/v1/components/{id}/operations/{operation_id}/executions

Execute an operation (service call or action goal).

  • Content-Type: application/json

  • 200: Service call completed (sync)

  • 202: Action goal accepted (async)

  • 400: Invalid input

  • 404: Operation not found

Service Example (synchronous):

curl -X POST http://localhost:8080/api/v1/components/calibration/operations/calibrate/executions \
  -H "Content-Type: application/json" \
  -d '{}'

Action Example (asynchronous):

curl -X POST http://localhost:8080/api/v1/components/calibration/operations/long_calibration/executions \
  -H "Content-Type: application/json" \
  -d '{"order": 10}'

Action Response (202 Accepted):

{
  "id": "abc123-def456",
  "status": "running"
}
GET /api/v1/components/{id}/operations/{operation_id}/executions

List all executions for an operation.

GET /api/v1/components/{id}/operations/{operation_id}/executions/{execution_id}

Get execution status and result.

Example Response (completed action):

{
  "execution_id": "abc123-def456",
  "status": "succeeded",
  "result": {"sequence": [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]},
  "feedback": [
    {"partial_sequence": [0, 1]},
    {"partial_sequence": [0, 1, 1, 2, 3]}
  ]
}
DELETE /api/v1/components/{id}/operations/{operation_id}/executions/{execution_id}

Cancel a running execution.

  • 204: Execution cancelled

  • 404: Execution not found

Configurations Endpoints

Manage ROS 2 node parameters.

GET /api/v1/components/{id}/configurations

List all parameters for an entity.

Example Response:

{
  "items": [
    {
      "name": "publish_rate",
      "value": 10.0,
      "type": "double",
      "description": "Publishing rate in Hz"
    },
    {
      "name": "sensor_id",
      "value": "sensor_001",
      "type": "string"
    }
  ],
  "x-medkit": {
    "entity_id": "temp_sensor",
    "total_count": 2
  }
}
GET /api/v1/components/{id}/configurations/{param_name}

Get a specific parameter value.

PUT /api/v1/components/{id}/configurations/{param_name}

Set a parameter value.

  • Content-Type: application/json

  • 200: Parameter updated

  • 400: Invalid value

  • 404: Parameter not found

Example:

curl -X PUT http://localhost:8080/api/v1/components/temp_sensor/configurations/publish_rate \
  -H "Content-Type: application/json" \
  -d '{"value": 20.0}'
DELETE /api/v1/components/{id}/configurations/{param_name}

Reset parameter to default value.

DELETE /api/v1/components/{id}/configurations

Reset all parameters to default values.

Resource Locking

SOVD resource locking for preventing concurrent modification of entity state. See Locking API for the full API reference.

Faults Endpoints

Query and manage faults.

Note

Faults are reported by ROS 2 nodes via the FaultReporter library, not via REST API. The gateway queries faults from the ros2_medkit_fault_manager node.

GET /api/v1/faults

List all faults across the system.

GET /api/v1/components/{id}/faults

List all faults for an entity.

Both endpoints accept an optional ?status= query parameter:

Value

Returns

(default)

PREFAILED + CONFIRMED (active faults)

pending

PREFAILED only

confirmed

CONFIRMED only

cleared

CLEARED + HEALED + PREPASSED (SOVD “cleared” semantics)

healed

HEALED + PREPASSED only

all

All statuses

Example Response:

{
  "items": [
    {
      "fault_code": "LIDAR_RANGE_INVALID",
      "severity": "ERROR",
      "message": "Invalid range configuration: min_range > max_range",
      "timestamp": "2025-01-15T10:30:00Z",
      "source": "lidar_driver"
    }
  ],
  "x-medkit": {
    "entity_id": "lidar_sensor",
    "total_count": 1
  }
}
GET /api/v1/components/{id}/faults/{fault_code}

Get details of a specific fault including environment data.

Example Response (200 OK):

{
  "item": {
    "code": "MOTOR_OVERHEAT",
    "fault_name": "Motor temperature exceeded threshold",
    "severity": 2,
    "status": {
      "aggregatedStatus": "active",
      "testFailed": "1",
      "confirmedDTC": "1",
      "pendingDTC": "0"
    }
  },
  "environment_data": {
    "extended_data_records": {
      "first_occurrence": "2026-02-04T10:30:00.000Z",
      "last_occurrence": "2026-02-04T10:35:00.000Z"
    },
    "snapshots": [
      {
        "type": "freeze_frame",
        "name": "motor_temperature",
        "data": 105.5,
        "x-medkit": {
          "topic": "/motor/temperature",
          "message_type": "sensor_msgs/msg/Temperature",
          "full_data": {"temperature": 105.5, "variance": 0.1},
          "captured_at": "2026-02-04T10:30:00.123Z"
        }
      },
      {
        "type": "rosbag",
        "name": "fault_recording",
        "bulk_data_uri": "/apps/motor_controller/bulk-data/rosbags/550e8400-e29b-41d4-a716-446655440000",
        "size_bytes": 1234567,
        "duration_sec": 6.0,
        "format": "mcap"
      }
    ]
  },
  "x-medkit": {
    "occurrence_count": 3,
    "reporting_sources": ["/powertrain/motor_controller"],
    "severity_label": "ERROR"
  }
}

Status Object:

The status object follows SOVD fault status specification:

  • aggregatedStatus: Overall status (active, passive, cleared)

  • testFailed: Test failed indicator (0 or 1)

  • confirmedDTC: Confirmed DTC indicator (0 or 1)

  • pendingDTC: Pending DTC indicator (0 or 1)

Snapshot Types:

  • freeze_frame: Topic data captured at fault confirmation

  • rosbag: Recording file available via bulk-data endpoint

DELETE /api/v1/components/{id}/faults/{fault_code}

Clear a fault.

  • 204: Fault cleared

  • 404: Fault not found

DELETE /api/v1/components/{id}/faults

Clear all faults for an entity.

Accepts the optional ?status= query parameter (same values as GET /faults). Without it, clears pending and confirmed faults.

  • 204: Faults cleared (or none to clear)

  • 400: Invalid status parameter

  • 503: Fault manager unavailable

DELETE /api/v1/faults

Clear all faults across the system (ros2_medkit extension, not SOVD).

Accepts the optional ?status= query parameter (same values as GET /faults). Without it, clears pending and confirmed faults.

  • 204: Faults cleared (or none to clear)

  • 400: Invalid status parameter

  • 503: Fault manager unavailable

Logs Endpoints

Query and configure the /rosout ring buffer for an entity. Supported entity types: areas (namespace prefix match), components (namespace prefix match), apps (exact FQN match), and functions (aggregated from hosted apps).

Note

By default, log entries are sourced from the /rosout ROS 2 topic. ros2_medkit retains the 200 most recent entries per node in an in-memory ring buffer (configurable via logs.buffer_size in gateway_params.yaml). A LogProvider plugin can replace the storage backend or take full ownership of the log pipeline (see plugin development docs).

GET /api/v1/components/{id}/logs

Query log entries for all nodes in the component namespace (prefix match).

GET /api/v1/apps/{id}/logs

Query log entries for the specific app node (exact match).

Query parameters:

Parameter

Description

severity

Minimum severity filter (debug | info | warning | error | fatal). The stricter of this parameter and the entity’s configured severity_filter is applied. Without this parameter, the entity’s configured severity_filter (default: debug) determines the minimum level. Empty or absent = use entity config only.

context

Substring filter applied to the log entry’s logger name (context.node in the response). Maximum length: 256 characters. Empty or absent = no filter.

Response 200:

{
  "items": [
    {
      "id": "log_42",
      "timestamp": "2026-01-15T10:30:00.123456789Z",
      "severity": "warning",
      "message": "Calibration drift detected",
      "context": {
        "node": "powertrain/engine/temp_sensor",
        "function": "read_sensor",
        "file": "temp_sensor.cpp",
        "line": 99
      }
    }
  ]
}

The context.function, context.file, and context.line fields are omitted when empty/zero.

Severity values map directly to the ROS 2 log levels:

Value

ROS 2 level

Meaning

debug

DEBUG (10)

Fine-grained diagnostic information

info

INFO (20)

Normal operational messages

warning

WARN (30)

Non-fatal anomalies

error

ERROR (40)

Errors that may require attention

fatal

FATAL (50)

Critical failures

GET /api/v1/components/{id}/logs/configuration / GET /api/v1/apps/{id}/logs/configuration

Return the current log configuration for the entity.

Response 200:

{
  "severity_filter": "debug",
  "max_entries": 100
}
PUT /api/v1/components/{id}/logs/configuration / PUT /api/v1/apps/{id}/logs/configuration

Update the log configuration for the entity. All body fields are optional.

Request body:

{
  "severity_filter": "warning",
  "max_entries": 500
}

severity_filter - minimum severity to return in query results (debug | info | warning | error | fatal). Entries below this level are excluded from queries. Default: debug.

max_entries - maximum number of entries returned per query. Must be between 1 and 10,000 (inclusive). Default: 100.

Response 204: No content.

  • 400: Invalid severity_filter or max_entries value

Bulk Data Endpoints

Access, upload, and delete large binary data (rosbags, calibration files, firmware, etc.) associated with entities. Read endpoints (GET) support all entity types. Write endpoints (POST, DELETE) are supported for components and apps only.

List Categories

GET /api/v1/{entity-path}/bulk-data

List available bulk-data categories for an entity. Returns the union of rosbag categories (from the fault manager) and configured categories (from bulk_data.categories).

Supported entity paths:

  • /apps/{app-id}

  • /components/{component-id}

  • /areas/{area-id}

  • /functions/{function-id}

  • /areas/{area-id}/subareas/{subarea-id}

  • /components/{component-id}/subcomponents/{subcomponent-id}

Example:

curl http://localhost:8080/api/v1/apps/motor_controller/bulk-data

Response (200 OK):

{
  "items": ["rosbags", "calibration", "firmware"]
}

List Bulk Data Items

GET /api/v1/{entity-path}/bulk-data/{category}

List all bulk-data items in a category for the entity.

Example:

curl http://localhost:8080/api/v1/apps/motor_controller/bulk-data/rosbags

Response (200 OK):

{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "MOTOR_OVERHEAT recording 2026-02-04T10:30:00Z",
      "mimetype": "application/x-mcap",
      "size": 1234567,
      "creation_date": "2026-02-04T10:30:00.000Z",
      "x-medkit": {
        "fault_code": "MOTOR_OVERHEAT",
        "duration_sec": 6.0,
        "format": "mcap"
      }
    }
  ]
}

Download Bulk Data

GET /api/v1/{entity-path}/bulk-data/{category}/{id}

Download a specific bulk-data file.

Response Headers:

  • Content-Type: application/x-mcap (MCAP format) or application/x-sqlite3 (db3)

  • Content-Disposition: attachment; filename="FAULT_CODE.mcap"

  • Access-Control-Expose-Headers: Content-Disposition

Example:

curl -O -J http://localhost:8080/api/v1/apps/motor_controller/bulk-data/rosbags/550e8400-e29b-41d4-a716-446655440000

Response Codes:

  • 200 OK: File content

  • 404 Not Found: Entity, category, or bulk-data ID not found

Upload Bulk Data

POST /api/v1/{entity-path}/bulk-data/{category}

Upload a new bulk-data file to the specified category. Files are sent as multipart/form-data. The rosbags category is read-only and cannot be used for uploads.

Supported entity types: components, apps only. Areas and functions return 405.

Form Fields:

Field

Required

Description

file

Yes

The file to upload (binary data with filename and content type).

description

No

Human-readable description of the file.

metadata

No

JSON string with arbitrary key-value metadata.

Example:

curl -X POST http://localhost:8080/api/v1/components/motor_controller/bulk-data/calibration \
  -F "file=@calibration_data.bin;type=application/octet-stream" \
  -F "description=Motor calibration parameters v2.1" \
  -F 'metadata={"version": "2.1", "author": "engineer_01"}'

Response (201 Created):

{
  "id": "calibration_1739612345000000000_ab12cd34",
  "name": "calibration_data.bin",
  "mimetype": "application/octet-stream",
  "size": 4096,
  "creation_date": "2026-03-15T14:30:00.000Z",
  "description": "Motor calibration parameters v2.1",
  "x-medkit": {
    "version": "2.1",
    "author": "engineer_01"
  }
}

Response Headers:

  • Location: /api/v1/components/motor_controller/bulk-data/calibration/calibration_1739612345000000000_ab12cd34

Error Responses:

  • 400 Bad Request: Missing file field, unknown category, or rosbags category

  • 405 Method Not Allowed: Upload attempted on areas or functions

  • 413 Payload Too Large: File exceeds bulk_data.max_upload_size

Delete Bulk Data

DELETE /api/v1/{entity-path}/bulk-data/{category}/{id}

Delete a specific bulk-data item. The rosbags category is managed by the fault manager and cannot be deleted via this endpoint.

Supported entity types: components, apps only. Areas and functions return 405.

Example:

curl -X DELETE http://localhost:8080/api/v1/components/motor_controller/bulk-data/calibration/calibration_1739612345000000000_ab12cd34

Response Codes:

  • 204 No Content: Item deleted successfully

  • 400 Bad Request: rosbags category (managed by fault manager)

  • 404 Not Found: Entity, category, or bulk-data ID not found

  • 405 Method Not Allowed: Delete attempted on areas or functions

Software Updates

Manage software update packages with an async prepare/execute lifecycle. The updates feature requires a plugin implementing UpdateProvider to be loaded via the plugin framework (see Server Configuration). Without such a plugin, all endpoints return 501 Not Implemented.

GET /api/v1/updates

List all registered update packages.

Query Parameters:

  • origin (optional): Filter by origin (remote or proximity)

  • target-version (optional): Filter by target version

Example Response (200 OK):

{
  "items": ["firmware-v2.1", "calibration-update-3"]
}
POST /api/v1/updates

Register a new update package.

Request Body:

{
  "id": "firmware-v2.1",
  "update_name": "Firmware Update v2.1",
  "automated": true,
  "origins": ["remote"],
  "duration": 600,
  "size": 52428800,
  "updated_components": ["ecu_main"],
  "affected_components": ["ecu_main", "ecu_secondary"]
}

Response (201 Created):

{
  "id": "firmware-v2.1"
}

Response Headers:

  • Location: /api/v1/updates/firmware-v2.1

GET /api/v1/updates/{id}

Get full metadata for a specific update package.

Response (200 OK):

Returns the JSON metadata as registered.

  • 404 Not Found: Package does not exist

DELETE /api/v1/updates/{id}

Delete an update package.

  • 204 No Content: Package deleted

  • 404 Not Found: Package does not exist

  • 409 Conflict: Operation in progress for this package

PUT /api/v1/updates/{id}/prepare

Trigger preparation of an update (download, verify, check dependencies). Runs asynchronously - poll the status endpoint for progress.

  • 202 Accepted: Preparation started

  • 404 Not Found: Package does not exist

  • 409 Conflict: Operation already in progress

Response Headers:

  • Location: /api/v1/updates/{id}/status

PUT /api/v1/updates/{id}/execute

Trigger execution of a prepared update (install). Only succeeds after prepare has completed.

  • 202 Accepted: Execution started

  • 400 Bad Request: Package not prepared

  • 404 Not Found: Package does not exist

  • 409 Conflict: Operation already in progress

Response Headers:

  • Location: /api/v1/updates/{id}/status

PUT /api/v1/updates/{id}/automated

Trigger automated update (prepare + execute in one step). Only works for packages that support automated mode.

  • 202 Accepted: Automated update started

  • 400 Bad Request: Package does not support automated mode

  • 404 Not Found: Package does not exist

  • 409 Conflict: Operation already in progress

Response Headers:

  • Location: /api/v1/updates/{id}/status

GET /api/v1/updates/{id}/status

Get the current status and progress of an update operation.

Example Response (200 OK):

{
  "status": "inProgress",
  "progress": 65,
  "sub_progress": [
    {"name": "download", "progress": 100},
    {"name": "verify", "progress": 30}
  ]
}

Status values: pending, inProgress, completed, failed

When status is failed, an error object is included:

{
  "status": "failed",
  "error": {
    "error_code": "internal-error",
    "message": "Download failed: connection timeout"
  }
}
  • 404 Not Found: No status available (package not found or no operation started)

Cyclic Subscriptions

Cyclic subscriptions provide periodic push-based delivery of any SOVD resource collection via Server-Sent Events (SSE). A client creates a subscription specifying the resource URI (data, faults, configurations, logs, or x- vendor extensions) and a delivery interval. The server then pushes the latest value at the requested frequency.

Subscriptions are temporary - they do not survive server restart.

Supported collections:

  • data - Topic data (requires a resource path, e.g. /data/temperature)

  • faults - Fault list (resource path optional, e.g. /faults or /faults/fault_001)

  • configurations - Parameter values (resource path optional)

  • logs - Application log entries from /rosout

  • x-* - Vendor extensions (e.g. x-medkit-graph)

Interval values:

  • fast - 50ms sampling period

  • normal - 200ms sampling period (default)

  • slow - 500ms sampling period

POST /api/v1/{entity_type}/{entity_id}/cyclic-subscriptions

Create a new cyclic subscription.

Applies to: /apps, /components, /functions

Request Body:

{
  "resource": "/api/v1/apps/temp_sensor/data/temperature",
  "protocol": "sse",
  "interval": "normal",
  "duration": 300
}

Fields:

  • resource (string, required): Full SOVD resource URI to observe (e.g. /api/v1/apps/{id}/data/{topic}, /api/v1/apps/{id}/faults, /api/v1/functions/{id}/x-medkit-graph)

  • protocol (string, optional): Transport protocol. Only "sse" supported. Default: "sse"

  • interval (string, required): One of fast, normal, slow

  • duration (integer, required): Subscription lifetime in seconds. Must be > 0 and <= sse.max_duration_sec (default: 3600)

Error responses:

  • 400 invalid-parameter - Invalid interval, duration <= 0, or duration exceeds max

  • 400 x-medkit-invalid-resource-uri - Malformed resource URI or path traversal

  • 400 x-medkit-entity-mismatch - Resource URI references different entity than route

  • 400 x-medkit-collection-not-supported - Entity doesn’t support the collection

  • 400 x-medkit-collection-not-available - No data provider registered for collection

  • 400 x-medkit-unsupported-protocol - Requested protocol not available

  • 503 service-unavailable - Max subscription capacity reached

Response 201 Created:

{
  "id": "sub_001",
  "observed_resource": "/api/v1/apps/temp_sensor/data/temperature",
  "event_source": "/api/v1/apps/temp_sensor/cyclic-subscriptions/sub_001/events",
  "protocol": "sse",
  "interval": "normal"
}
GET /api/v1/{entity_type}/{entity_id}/cyclic-subscriptions

List all active cyclic subscriptions for an entity. Returns {"items": [...]}.

GET /api/v1/{entity_type}/{entity_id}/cyclic-subscriptions/{id}

Get details of a single subscription.

PUT /api/v1/{entity_type}/{entity_id}/cyclic-subscriptions/{id}

Update interval and/or duration of an existing subscription. Only provided fields are updated. Updating duration resets the expiry timer from the current time (not from the original creation time).

Request Body:

{
  "interval": "fast",
  "duration": 600
}
DELETE /api/v1/{entity_type}/{entity_id}/cyclic-subscriptions/{id}

Cancel and remove a subscription. Returns 204 No Content.

GET /api/v1/{entity_type}/{entity_id}/cyclic-subscriptions/{id}/events

SSE event stream. Connect to receive periodic data updates.

Response Headers:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Event Format (EventEnvelope):

data: {"timestamp":"2026-02-14T10:30:00.250Z","payload":{"id":"/temperature","data":{"data":23.5}}}

The stream auto-closes when the duration expires, the client disconnects, or the subscription is deleted.

Multi-collection examples:

Subscribe to faults on a component:

{
  "resource": "/api/v1/components/ecu1/faults",
  "interval": "slow",
  "duration": 600
}

Subscribe to a specific configuration parameter:

{
  "resource": "/api/v1/apps/temp_sensor/configurations/calibration_offset",
  "interval": "normal",
  "duration": 120
}

Scripts

Upload, manage, and execute diagnostic scripts on entities. (ISO 17978-3, 7.15)

Scripts are available on Components and Apps entity types. The feature must be enabled by setting scripts.scripts_dir in the gateway configuration.

Upload Script

POST /api/v1/{entity_type}/{entity_id}/scripts

Upload a diagnostic script via multipart/form-data.

  • file (required): The script file (Python, bash, or sh)

  • metadata (optional): JSON with name, description, parameters_schema

Response: 201 Created with Location header pointing to the new script.

Note

Uploads can be disabled by setting scripts.allow_uploads: false in the gateway configuration. When disabled, POST returns 400. Pre-deployed manifest scripts remain available for execution.

List Scripts

GET /api/v1/{entity_type}/{entity_id}/scripts

List all scripts for an entity. Returns {"items": [...]}.

Get Script

GET /api/v1/{entity_type}/{entity_id}/scripts/{script_id}

Get metadata for a specific script.

Delete Script

DELETE /api/v1/{entity_type}/{entity_id}/scripts/{script_id}

Delete an uploaded script. Returns 204 No Content. Returns 409 if the script is manifest-managed or currently executing.

Start Execution

POST /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions

Start a new execution of a script.

Request Body:

{
  "execution_type": "now",
  "parameters": {"threshold": 0.1}
}

Attribute

Type

Conv

Description

execution_type

string

M

When to run: now, on_restart, now_and_on_restart, once_on_restart

parameters

object

O

Input parameters for the script

proximity_response

string

O

Co-location proof token

Note

The built-in script backend supports only now. Other execution types (on_restart, now_and_on_restart, once_on_restart) require a plugin-provided ScriptProvider and will return 400 invalid-parameter if not supported.

Response: 202 Accepted with Location header pointing to the execution status.

Get Execution Status

GET /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}

Poll the status of a script execution.

Status values: prepared, running, completed, failed, terminated

Terminate Execution

PUT /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}

Send a termination action to a running execution.

Request Body:

{"action": "stop"}

Action values: stop (SIGTERM), forced_termination (SIGKILL).

Delete Execution

DELETE /api/v1/{entity_type}/{entity_id}/scripts/{script_id}/executions/{execution_id}

Remove a completed/terminated execution resource. Returns 204 No Content. Returns 409 if the execution is still running.

Triggers

Triggers provide condition-based push notifications for resource changes via Server-Sent Events (SSE). Unlike cyclic subscriptions - which poll a resource at a fixed interval and push every sample - triggers evaluate a condition against each change and only fire when the condition is met.

Key differences from Cyclic Subscriptions:

  • Cyclic subscriptions push data at a fixed interval (fast/normal/slow) regardless of whether the value changed

  • Triggers are event-driven: they only fire when a specific condition is satisfied (e.g., value changed, entered a range, reached a threshold)

  • Triggers support persistence across gateway restarts (persistent: true)

  • Triggers can be one-shot (fire once, then auto-terminate) or multishot (continuous)

Supported entity types: /areas, /components, /apps, /functions

Note

ros2_medkit extension: SOVD defines triggers for apps and components only. ros2_medkit extends trigger support to areas and functions, allowing hierarchy-scoped monitoring. Area-level triggers catch changes from all descendant entities within the area.

Observable resource collections:

  • data - Topic data changes (driven by TriggerTopicSubscriber)

  • faults - Fault state transitions (created, updated, cleared)

  • operations - Operation execution completions

  • updates - Software update status changes

  • logs - Log entries matching configured severity (x-medkit extension)

Create Trigger

POST /api/v1/{entity_type}/{entity_id}/triggers

Create a new condition-based trigger.

Request Body:

{
  "resource": "/api/v1/apps/temp_sensor/data/powertrain%2Fengine%2Ftemperature",
  "trigger_condition": {
    "condition_type": "LeaveRange",
    "lower_bound": 20.0,
    "upper_bound": 80.0
  },
  "path": "/data",
  "protocol": "sse",
  "multishot": true,
  "persistent": false,
  "lifetime": 300,
  "log_settings": {
    "severity": "warning",
    "marker": "Temperature threshold exceeded"
  }
}

Fields:

Field

Required

Description

resource

Yes

Full SOVD resource URI to observe (e.g. /api/v1/apps/{id}/data/{topic}, /api/v1/apps/{id}/faults, /api/v1/areas/{id}/faults). Must reference the same entity as the route.

trigger_condition

Yes

Object with condition_type and condition-specific parameters. See Trigger Conditions below.

path

No

JSON Pointer within the resource payload to evaluate. When set, the condition is evaluated against the value at this path instead of the full payload.

protocol

No

Transport protocol. Only "sse" is supported. Default: "sse".

multishot

No

If true, the trigger fires repeatedly. If false, the trigger auto-terminates after the first event. Default: false.

persistent

No

If true, the trigger survives gateway restarts (when on_restart_behavior is "restore"). Default: false.

lifetime

No

Time-to-live in seconds. The trigger auto-terminates after this duration. Must be a positive integer. Omit for no expiry.

log_settings

No

Temporary log entry injected when the trigger fires. Accepts severity (log level: debug, info, warning, error, fatal; default: info) and marker (descriptive message text; default: "Trigger fired"). The log entry includes trigger metadata (trigger ID, condition type, resource URI).

Response 201 Created:

{
  "id": "trig_001",
  "status": "active",
  "observed_resource": "/api/v1/apps/temp_sensor/data/powertrain%2Fengine%2Ftemperature",
  "event_source": "/api/v1/apps/temp_sensor/triggers/trig_001/events",
  "protocol": "sse",
  "trigger_condition": {
    "condition_type": "LeaveRange",
    "lower_bound": 20.0,
    "upper_bound": 80.0
  },
  "multishot": true,
  "persistent": false,
  "lifetime": 300
}

Error Responses:

  • 400 invalid-parameter - Missing or invalid resource, trigger_condition, condition_type, lifetime, or condition-specific parameters

  • 400 x-medkit-invalid-resource-uri - Malformed resource URI or path traversal

  • 400 x-medkit-entity-mismatch - Resource URI references a different entity than the route

  • 503 service-unavailable - Maximum trigger capacity reached (configurable via triggers.max_triggers)

List Triggers

GET /api/v1/{entity_type}/{entity_id}/triggers

List all triggers for an entity.

Response 200:

{
  "items": [
    {
      "id": "trig_001",
      "status": "active",
      "observed_resource": "/api/v1/apps/temp_sensor/faults",
      "event_source": "/api/v1/apps/temp_sensor/triggers/trig_001/events",
      "protocol": "sse",
      "trigger_condition": {"condition_type": "OnChange"},
      "multishot": true,
      "persistent": false,
      "lifetime": 300
    }
  ]
}

Get Trigger

GET /api/v1/{entity_type}/{entity_id}/triggers/{trigger_id}

Get details of a single trigger.

Response 200: Same schema as creation response.

  • 404 resource-not-found - Trigger not found or belongs to a different entity

Update Trigger

PUT /api/v1/{entity_type}/{entity_id}/triggers/{trigger_id}

Update the lifetime of an existing trigger. Updating lifetime resets the expiry timer from the current time.

Request Body:

{
  "lifetime": 600
}

Response 200: Updated trigger object (same schema as creation response).

  • 400 invalid-parameter - Missing or invalid lifetime

  • 404 resource-not-found - Trigger not found

Delete Trigger

DELETE /api/v1/{entity_type}/{entity_id}/triggers/{trigger_id}

Remove a trigger. Any active SSE connection for this trigger is closed.

  • 204 No Content - Trigger deleted

  • 404 resource-not-found - Trigger not found

Trigger Events (SSE Stream)

GET /api/v1/{entity_type}/{entity_id}/triggers/{trigger_id}/events

SSE event stream for a trigger. Connect to receive events when the trigger condition is met. The stream sends keepalive comments every 15 seconds.

Response Headers:

Content-Type: text/event-stream
Cache-Control: no-cache

EventEnvelope format:

Each event is delivered as an SSE data: frame containing a JSON EventEnvelope:

data: {"timestamp":"2026-03-19T10:30:00.250Z","payload":{"data":{"data":85.5}}}

When an error occurs during evaluation:

data: {"timestamp":"2026-03-19T10:30:00.250Z","error":"Failed to read resource"}

EventEnvelope fields:

  • timestamp (string) - ISO 8601 timestamp of when the event was generated

  • payload (object) - The resource value that satisfied the condition (present on success)

  • error (string) - Error description (present on failure, mutually exclusive with payload)

The stream closes when:

  • The trigger’s lifetime expires

  • The trigger is deleted

  • A one-shot trigger fires (multishot: false)

  • The client disconnects

  • The gateway shuts down

  • Maximum SSE client limit is reached (503 on connect)

Example:

curl -N http://localhost:8080/api/v1/apps/temp_sensor/triggers/trig_001/events
  • 404 resource-not-found - Trigger not found or expired

  • 503 service-unavailable - Maximum SSE client limit reached

Trigger Conditions

The trigger_condition object in the creation request specifies when the trigger fires. Four standard condition types are supported:

Condition Type

Parameters

Behavior

OnChange

(none)

Fires whenever the current value differs from the previous value. First evaluation always fires.

OnChangeTo

target_value (any JSON value, required)

Fires when the current value equals the target AND differs from the previous value. First evaluation checks target only.

EnterRange

lower_bound (number, required), upper_bound (number, required)

Fires when a numeric value transitions from outside the inclusive range [lower_bound, upper_bound] to inside it. Requires a previous value (first evaluation does not fire).

LeaveRange

lower_bound (number, required), upper_bound (number, required)

Fires when a numeric value transitions from inside the inclusive range [lower_bound, upper_bound] to outside it. Requires a previous value (first evaluation does not fire).

Plugins can register custom condition evaluators with x- prefixed names (e.g., x-threshold-count) via the ConditionRegistry.

Configuration

Configure triggers in gateway_params.yaml:

ros2_medkit_gateway:
  ros__parameters:
    triggers:
      # Enable/disable the trigger subsystem (default: true)
      # When false, trigger endpoints return 501
      enabled: true

      # Maximum concurrent triggers across all entities (default: 1000)
      # Returns HTTP 503 when this limit is reached
      max_triggers: 1000

      # Behavior on gateway restart for persistent triggers
      # "reset": Clear all triggers on restart (default)
      # "restore": Reload persistent triggers from storage
      on_restart_behavior: "reset"

      # Trigger persistence storage
      storage:
        # Path to SQLite database for persistent triggers
        # Empty string = in-memory only (default)
        # Example: "/var/lib/ros2_medkit/triggers.db"
        path: ""

Persistence

Triggers created with "persistent": true are stored in a SQLite database. On gateway restart, their behavior depends on the on_restart_behavior configuration:

  • reset (default): All triggers are cleared on restart, regardless of the persistent flag. This is the safest option for development.

  • restore: Persistent triggers are reloaded from the database. Their previous_value state is preserved, allowing range-based conditions (EnterRange, LeaveRange) to evaluate correctly without losing context.

Non-persistent triggers are always cleared on restart.

Rate Limiting

The gateway supports token-bucket-based rate limiting to protect endpoints from abuse. Rate limiting is disabled by default and can be enabled via configuration parameters.

Configuration

You can configure global and per-client RPM (requests per minute) limits:

  • rate_limiting.enabled: true to enable.

  • rate_limiting.global_requests_per_minute: Overarching limit across all clients.

  • rate_limiting.client_requests_per_minute: Limit per individual client IP.

Endpoint limits can also be overridden with patterns:

  • rate_limiting.endpoint_limits: List of "pattern:rpm" strings. For example, ["/api/v1/*/operations/*:10"] limits execution calls without affecting other data endpoints.

Response Headers

When rate limiting is enabled, the gateway includes the following HTTP response headers on every check:

  • X-RateLimit-Limit: The effective RPM limit applied.

  • X-RateLimit-Remaining: Number of requests remaining in the current minute window.

  • X-RateLimit-Reset: Unix epoch time (in seconds) when the limit bucket resets.

Rejection (429 Too Many Requests)

If a request exceeds the available tokens, it is rejected with an HTTP 429 status code and a Retry-After header indicating the number of seconds to wait before retrying.

Example Response:

{
  "error_code": 429,
  "message": "Too many requests. Please retry after 10 seconds.",
  "parameters": {
    "retry_after": 10,
    "limit": 60,
    "reset": 1739612355
  }
}

Authentication Endpoints

JWT-based authentication with Role-Based Access Control (RBAC).

See also

Configuring Authentication for configuration details.

POST /api/v1/auth/authorize

Authenticate with client credentials.

Request:

{
  "grant_type": "client_credentials",
  "client_id": "admin",
  "client_secret": "admin_secret_key"
}

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
  "scope": "admin"
}
POST /api/v1/auth/token

Refresh access token.

Request:

{
  "grant_type": "refresh_token",
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g..."
}
POST /api/v1/auth/revoke

Revoke a token.

Request:

{"token": "dGhpcyBpcyBhIHJlZnJlc2g..."}

Vendor Extension Endpoints (Plugins)

Plugin-registered endpoints use the x-medkit- prefix following the SOVD vendor extension mechanism. These endpoints are only available when the corresponding plugin is loaded (see Linux Introspection Plugins).

Warning

The procfs plugin exposes process command lines (/proc/{pid}/cmdline) via HTTP. Command lines may contain sensitive data (API keys, passwords passed as arguments). Enable authentication when using the procfs plugin in production environments.

Note

Vendor extension endpoints are registered dynamically by plugins. They do not appear in the GET / root endpoint list. Use entity capability responses (GET /apps/{id}, GET /components/{id}) to discover available extensions via the capabilities field.

Linux Process Introspection (x-medkit-procfs)

Requires: procfs_introspection plugin.

GET /api/v1/apps/{id}/x-medkit-procfs

Get process-level information for a single app.

Response 200:

{
  "pid": 1234,
  "ppid": 1,
  "state": "S",
  "exe": "/usr/bin/talker",
  "cmdline": "/usr/bin/talker --ros-args __node:=talker __ns:=/demo",
  "rss_bytes": 524288,
  "vm_size_bytes": 2097152,
  "threads": 4,
  "cpu_user_ticks": 1520,
  "cpu_system_ticks": 340,
  "cpu_user_seconds": 15.2,
  "cpu_system_seconds": 3.4,
  "uptime_seconds": 123.45
}
  • 404: Process not found (node not running or PID cache miss)

  • 503: Failed to read process information

GET /api/v1/components/{id}/x-medkit-procfs

Aggregate process info for all apps in the component. Processes are deduplicated by PID (multiple nodes in the same process appear once).

Response 200:

{
  "processes": [
    {
      "pid": 1234,
      "node_ids": ["talker", "listener"],
      "...": "same fields as app endpoint"
    }
  ]
}

Systemd Unit Introspection (x-medkit-systemd)

Requires: systemd_introspection plugin and libsystemd.

GET /api/v1/apps/{id}/x-medkit-systemd

Get systemd unit information for the app’s process.

Response 200:

{
  "unit": "ros2-talker.service",
  "unit_type": "service",
  "active_state": "active",
  "sub_state": "running",
  "restart_count": 2,
  "watchdog_usec": 5000000
}

restart_count and watchdog_usec are only meaningful for service units. For other unit types (timer, mount, etc.) they are always 0.

  • 404: Process not found or not managed by a systemd unit

  • 503: Failed to query systemd properties

GET /api/v1/components/{id}/x-medkit-systemd

Aggregate systemd unit info for all apps in the component. Units are deduplicated by unit name.

Response 200:

{
  "units": [
    {
      "unit": "ros2-talker.service",
      "node_ids": ["talker", "listener"],
      "...": "same fields as app endpoint"
    }
  ]
}

Container Introspection (x-medkit-container)

Requires: container_introspection plugin. Only supports cgroup v2 (Ubuntu 22.04+, Fedora 31+).

GET /api/v1/apps/{id}/x-medkit-container

Get container information for the app’s process.

Response 200:

{
  "container_id": "a1b2c3d4e5f6...",
  "runtime": "docker",
  "memory_limit_bytes": 1073741824,
  "cpu_quota_us": 100000,
  "cpu_period_us": 100000
}

Fields memory_limit_bytes, cpu_quota_us, and cpu_period_us are only present when the container has resource limits configured.

  • 404: Process not found or not running in a container

  • 503: Failed to read cgroup information

GET /api/v1/components/{id}/x-medkit-container

Aggregate container info for all apps in the component. Containers are deduplicated by container ID.

Response 200:

{
  "containers": [
    {
      "container_id": "a1b2c3d4e5f6...",
      "node_ids": ["talker", "listener"],
      "...": "same fields as app endpoint"
    }
  ]
}

x-medkit-topic-beacon

Provided by the ros2_medkit_topic_beacon plugin (not available when the plugin is not loaded). Returns beacon metadata for an entity populated from MedkitDiscoveryHint messages published by the entity’s node via a ROS 2 topic (push-based).

GET /api/v1/apps/{id}/x-medkit-topic-beacon

GET /api/v1/components/{id}/x-medkit-topic-beacon

Example:

curl http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-topic-beacon

Response (200 OK):

{
  "entity_id": "engine_temp_sensor",
  "status": "active",
  "age_sec": 1.234,
  "stable_id": "",
  "display_name": "Engine Temperature Sensor",
  "transport_type": "shared_memory",
  "negotiated_format": "",
  "process_id": 12345,
  "process_name": "sensor_node",
  "hostname": "robot-1",
  "component_id": "powertrain",
  "function_ids": ["monitoring"],
  "depends_on": [],
  "metadata": {"custom_key": "custom_value"}
}

Status values:

  • active - Hint is within the configured beacon_ttl_sec

  • stale - Hint is past TTL but within beacon_expiry_sec

Notes:

  • age_sec is the elapsed time in seconds since the last hint was received.

  • When no beacon data exists for an entity, the endpoint returns 404 with error code x-medkit-beacon-not-found (not an "unknown" status).

Response Codes:

  • 200 OK - Beacon data found and returned

  • 404 Not Found (code: ERR_ENTITY_NOT_FOUND) - Entity does not exist

  • 404 Not Found (code: x-medkit-beacon-not-found) - Entity exists but no beacon data received

x-medkit-param-beacon

Provided by the ros2_medkit_param_beacon plugin (not available when the plugin is not loaded). Returns beacon metadata for an entity populated by polling ROS 2 node parameters matching a configured prefix (pull-based).

GET /api/v1/apps/{id}/x-medkit-param-beacon

GET /api/v1/components/{id}/x-medkit-param-beacon

Example:

curl http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-param-beacon

Response (200 OK):

The response schema is identical to x-medkit-topic-beacon. See above for the full field listing.

Response Codes:

  • 200 OK - Beacon data found and returned

  • 404 Not Found (code: ERR_ENTITY_NOT_FOUND) - Entity does not exist

  • 404 Not Found (code: x-medkit-beacon-not-found) - Entity exists but no beacon data received

Error Responses

All error responses follow a consistent format:

{
  "error": {
    "code": "ERR_ENTITY_NOT_FOUND",
    "message": "Entity not found",
    "details": {
      "entity_id": "unknown_component"
    }
  }
}

Common Error Codes

Error Code

HTTP Status

Description

ERR_ENTITY_NOT_FOUND

404

The requested entity does not exist

ERR_RESOURCE_NOT_FOUND

404

The requested resource (topic, service, parameter) does not exist

ERR_INVALID_INPUT

400

Invalid request body or parameters

ERR_INVALID_ENTITY_ID

400

Entity ID contains invalid characters

ERR_OPERATION_FAILED

500

Operation failed during execution

ERR_TIMEOUT

504

Operation timed out

ERR_UNAUTHORIZED

401

Authentication required or token invalid

ERR_FORBIDDEN

403

Insufficient permissions for this operation

URL Encoding

Topic and parameter paths containing / must be URL-encoded:

Original Path

URL Encoded

/powertrain/engine/temperature

powertrain%2Fengine%2Ftemperature

/chassis/brakes/command

chassis%2Fbrakes%2Fcommand

SOVD Compliance

The gateway implements a pragmatic subset of the SOVD (Service-Oriented Vehicle Diagnostics) standard. We follow SOVD where it matters for interoperability - endpoint contracts, data model, entity hierarchy - but extend it where ROS 2 use cases benefit.

SOVD-Aligned Capabilities:

  • Discovery (/areas, /components, /apps, /functions)

  • Data access (/data) with topic sampling and JSON serialization

  • Operations (/operations, /executions) with async action support

  • Configurations (/configurations)

  • Faults (/faults) with environment_data and SOVD status object

  • Logs (/logs) with severity filtering and per-entity configuration

  • Bulk Data (/bulk-data) with custom categories and rosbag downloads

  • Software Updates (/updates) with async prepare/execute lifecycle

  • Cyclic Subscriptions (/cyclic-subscriptions) with SSE-based delivery

  • Scripts (/scripts) with upload, execution, and lifecycle management

  • Triggers (/triggers) with condition-based push notifications

Pragmatic Extensions:

The SOVD spec defines resource collections only for apps and components. ros2_medkit extends this to areas and functions where aggregation makes practical sense:

Resource Collection Support Matrix

Resource

Areas

Components

Apps

Functions

SOVD Spec

data

aggregated

yes

yes

aggregated

apps, components

operations

aggregated

yes

yes

aggregated

apps, components

configurations

aggregated

yes

yes

aggregated

apps, components

faults

aggregated

yes

yes

aggregated

apps, components

logs

prefix match

prefix match

exact match

from hosts

apps, components

bulk-data

read-only

full CRUD

full CRUD

read-only

apps, components

cyclic-subscriptions

-

yes

yes

yes

apps, components

scripts

-

yes

yes

-

apps, components

triggers

yes (x-medkit)

yes

yes

yes (x-medkit)

apps, components

Other extensions beyond SOVD:

  • Vendor extension fields using x-medkit prefix (per SOVD extension mechanism)

  • DELETE /faults - Clear all faults globally

  • GET /faults/stream - SSE real-time fault notifications

  • /health - Health check with discovery pipeline diagnostics

  • /version-info - Gateway version information

  • /docs - OpenAPI capability description

  • SSE fault streaming - Real-time fault notifications

  • x-medkit extension fields in responses

Capability Description (OpenAPI Docs)

The gateway provides self-describing OpenAPI 3.1.0 capability descriptions at any level of the API hierarchy. Append /docs to any valid path to receive a context-scoped OpenAPI spec describing the available operations at that level.

GET /api/v1/docs

Returns the full OpenAPI spec for the gateway root, including all server-level endpoints, entity collections, and global resources.

GET /api/v1/{entity-collection}/docs

Returns a spec scoped to the entity collection (e.g., /apps/docs, /components/docs). Includes collection listing and detail endpoints.

GET /api/v1/{entity-type}/{entity-id}/docs

Returns a spec for a specific entity, including all resource collection endpoints supported by that entity (data, operations, configurations, faults, logs, bulk-data, cyclic-subscriptions, triggers).

GET /api/v1/{entity-type}/{entity-id}/{resource}/docs

Returns a spec for a specific resource collection, with detailed schemas for each resource item.

Features:

  • Specs include SOVD extensions (x-sovd-version, x-sovd-data-category)

  • Entity-level specs reflect actual capabilities from the runtime entity cache

  • Specs are cached per entity cache generation for performance

  • Plugin-registered vendor routes appear in path-scoped specs when the requested path matches a plugin route prefix (not in the root spec)

Configuration:

  • docs.enabled (bool, default: true) - Set to false to disable the /docs endpoints. Returns 501 when disabled.

Swagger UI (optional):

When built with -DENABLE_SWAGGER_UI=ON, the gateway serves an interactive Swagger UI at /api/v1/swagger-ui with embedded assets (no CDN dependency).

Error Responses:

  • 404: No capability description available for the requested path

  • 501: Capability description is disabled (docs.enabled=false)

See Also