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-infoGet 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/healthHealth check endpoint. Returns HTTP 200 if gateway is operational.
Discovery Endpoints
Areas
GET /api/v1/areasList 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}/containsList components contained in this area.
GET /api/v1/areas/{area_id}/componentsList 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/componentsList 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}/hostsList apps hosted on this component (SOVD 7.6.2.4).
GET /api/v1/components/{component_id}/depends-onList component dependencies.
Apps
GET /api/v1/appsList 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-onReturn the parent component that hosts this app.
The response follows the standard
itemswrapper and returns:0items when the app has no associated host component1item when the host component is resolved1item withx-medkit.missing=truewhen 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 foundwithparameters.app_id.
Functions
GET /api/v1/functionsList all functions (requires manifest mode or hybrid mode).
GET /api/v1/functions/{function_id}Get function capabilities.
GET /api/v1/functions/{function_id}/hostsList apps that host this function.
GET /api/v1/functions/{function_id}/x-medkit-graphGet 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 ofhealthy,degraded,brokennode_status: per-node reachability, one ofreachable,unreachablemetrics_status: per-edge telemetry state, one ofpending,active,errorerror_reason: present whenmetrics_statusiserror; one ofnode_offline,topic_stale,no_data_source
Note
ros2_medkit extension: Functions support resource collections beyond the SOVD spec.
/dataand/operationsaggregate from hosted apps (per SOVD). Additionally,/configurations,/faults,/logsaggregate from hosts, read-only/bulk-datais available,/cyclic-subscriptionsand/triggersare supported, and the vendor resource/x-medkit-graphexposes 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}/dataRead 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%2FtemperaturePUT /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}/operationsList 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}/executionsExecute 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}/executionsList 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}/configurationsList 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}/configurationsReset 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/faultsList all faults across the system.
GET /api/v1/components/{id}/faultsList all faults for an entity.
Both endpoints accept an optional
?status=query parameter:Value
Returns
(default)
PREFAILED+CONFIRMED(active faults)pendingPREFAILEDonlyconfirmedCONFIRMEDonlyclearedCLEARED+HEALED+PREPASSED(SOVD “cleared” semantics)healedHEALED+PREPASSEDonlyallAll 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
statusobject follows SOVD fault status specification:aggregatedStatus: Overall status (active,passive,cleared)testFailed: Test failed indicator (0or1)confirmedDTC: Confirmed DTC indicator (0or1)pendingDTC: Pending DTC indicator (0or1)
Snapshot Types:
freeze_frame: Topic data captured at fault confirmationrosbag: 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}/faultsClear all faults for an entity.
Accepts the optional
?status=query parameter (same values asGET /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/faultsClear all faults across the system (ros2_medkit extension, not SOVD).
Accepts the optional
?status=query parameter (same values asGET /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}/logsQuery log entries for all nodes in the component namespace (prefix match).
GET /api/v1/apps/{id}/logsQuery log entries for the specific app node (exact match).
Query parameters:
Parameter |
Description |
|---|---|
|
Minimum severity filter ( |
|
Substring filter applied to the log entry’s logger name ( |
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 (10) |
Fine-grained diagnostic information |
|
INFO (20) |
Normal operational messages |
|
WARN (30) |
Non-fatal anomalies |
|
ERROR (40) |
Errors that may require attention |
|
FATAL (50) |
Critical failures |
GET /api/v1/components/{id}/logs/configuration/GET /api/v1/apps/{id}/logs/configurationReturn 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/configurationUpdate 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_filterormax_entriesvalue
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) orapplication/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 |
|---|---|---|
|
Yes |
The file to upload (binary data with filename and content type). |
|
No |
Human-readable description of the file. |
|
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
filefield, unknown category, orrosbagscategory405 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:
rosbagscategory (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/updatesList all registered update packages.
Query Parameters:
origin(optional): Filter by origin (remoteorproximity)target-version(optional): Filter by target version
Example Response (200 OK):
{ "items": ["firmware-v2.1", "calibration-update-3"] }
POST /api/v1/updatesRegister 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}/prepareTrigger 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}/executeTrigger 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}/automatedTrigger 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}/statusGet 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,failedWhen
statusisfailed, anerrorobject 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./faultsor/faults/fault_001)configurations- Parameter values (resource path optional)logs- Application log entries from/rosoutx-*- Vendor extensions (e.g.x-medkit-graph)
Interval values:
fast- 50ms sampling periodnormal- 200ms sampling period (default)slow- 500ms sampling period
POST /api/v1/{entity_type}/{entity_id}/cyclic-subscriptionsCreate a new cyclic subscription.
Applies to:
/apps,/components,/functionsRequest 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 offast,normal,slowduration(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 max400
x-medkit-invalid-resource-uri- Malformed resource URI or path traversal400
x-medkit-entity-mismatch- Resource URI references different entity than route400
x-medkit-collection-not-supported- Entity doesn’t support the collection400
x-medkit-collection-not-available- No data provider registered for collection400
x-medkit-unsupported-protocol- Requested protocol not available503
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-subscriptionsList 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
intervaland/ordurationof an existing subscription. Only provided fields are updated. Updatingdurationresets 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}/eventsSSE 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}/scriptsUpload 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
Locationheader pointing to the new script.Note
Uploads can be disabled by setting
scripts.allow_uploads: falsein 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}/scriptsList 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}/executionsStart a new execution of a script.
Request Body:
{ "execution_type": "now", "parameters": {"threshold": 0.1} }
Attribute
Type
Conv
Description
execution_typestring
M
When to run:
now,on_restart,now_and_on_restart,once_on_restartparametersobject
O
Input parameters for the script
proximity_responsestring
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 400invalid-parameterif not supported.Response: 202 Accepted with
Locationheader 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 changedTriggers 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 byTriggerTopicSubscriber)faults- Fault state transitions (created, updated, cleared)operations- Operation execution completionsupdates- Software update status changeslogs- Log entries matching configured severity (x-medkit extension)
Create Trigger
POST /api/v1/{entity_type}/{entity_id}/triggersCreate 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
resourceYes
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_conditionYes
Object with
condition_typeand condition-specific parameters. See Trigger Conditions below.pathNo
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.
protocolNo
Transport protocol. Only
"sse"is supported. Default:"sse".multishotNo
If
true, the trigger fires repeatedly. Iffalse, the trigger auto-terminates after the first event. Default:false.persistentNo
If
true, the trigger survives gateway restarts (whenon_restart_behavioris"restore"). Default:false.lifetimeNo
Time-to-live in seconds. The trigger auto-terminates after this duration. Must be a positive integer. Omit for no expiry.
log_settingsNo
Temporary log entry injected when the trigger fires. Accepts
severity(log level:debug,info,warning,error,fatal; default:info) andmarker(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 invalidresource,trigger_condition,condition_type,lifetime, or condition-specific parameters400
x-medkit-invalid-resource-uri- Malformed resource URI or path traversal400
x-medkit-entity-mismatch- Resource URI references a different entity than the route503
service-unavailable- Maximum trigger capacity reached (configurable viatriggers.max_triggers)
List Triggers
GET /api/v1/{entity_type}/{entity_id}/triggersList 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
lifetimeresets 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 invalidlifetime404
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}/eventsSSE 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 generatedpayload(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
lifetimeexpiresThe 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 expired503
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 |
|---|---|---|
|
(none) |
Fires whenever the current value differs from the previous value. First evaluation always fires. |
|
|
Fires when the current value equals the target AND differs from the previous value. First evaluation checks target only. |
|
|
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). |
|
|
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
persistentflag. This is the safest option for development.restore: Persistent triggers are reloaded from the database. Their
previous_valuestate 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:trueto 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/authorizeAuthenticate 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/tokenRefresh access token.
Request:
{ "grant_type": "refresh_token", "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g..." }
POST /api/v1/auth/revokeRevoke 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-procfsGet 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-procfsAggregate 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-systemdGet 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_countandwatchdog_usecare 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-systemdAggregate 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-containerGet 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, andcpu_period_usare 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-containerAggregate 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 configuredbeacon_ttl_secstale- Hint is past TTL but withinbeacon_expiry_sec
Notes:
age_secis 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 exist404 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 exist404 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 |
|---|---|---|
|
404 |
The requested entity does not exist |
|
404 |
The requested resource (topic, service, parameter) does not exist |
|
400 |
Invalid request body or parameters |
|
400 |
Entity ID contains invalid characters |
|
500 |
Operation failed during execution |
|
504 |
Operation timed out |
|
401 |
Authentication required or token invalid |
|
403 |
Insufficient permissions for this operation |
URL Encoding
Topic and parameter paths containing / must be URL-encoded:
Original Path |
URL Encoded |
|---|---|
|
|
|
|
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 serializationOperations (
/operations,/executions) with async action supportConfigurations (
/configurations)Faults (
/faults) withenvironment_dataand SOVD status objectLogs (
/logs) with severity filtering and per-entity configurationBulk Data (
/bulk-data) with custom categories and rosbag downloadsSoftware Updates (
/updates) with async prepare/execute lifecycleCyclic Subscriptions (
/cyclic-subscriptions) with SSE-based deliveryScripts (
/scripts) with upload, execution, and lifecycle managementTriggers (
/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 |
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-medkitprefix (per SOVD extension mechanism)DELETE /faults- Clear all faults globallyGET /faults/stream- SSE real-time fault notifications/health- Health check with discovery pipeline diagnostics/version-info- Gateway version information/docs- OpenAPI capability descriptionSSE fault streaming - Real-time fault notifications
x-medkitextension 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/docsReturns the full OpenAPI spec for the gateway root, including all server-level endpoints, entity collections, and global resources.
GET /api/v1/{entity-collection}/docsReturns 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}/docsReturns 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}/docsReturns 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 tofalseto disable the/docsendpoints. 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
Discovery Options Reference for merge pipeline configuration
Configuring Authentication - Configure authentication
Server Configuration - Server configuration options