This tutorial will walk you through using ros2_medkit to discover and interact with ROS 2 nodes through the REST API gateway.
Table of Contents
ros2_medkit provides a REST API gateway that exposes your ROS 2 system for external tools, web interfaces, and remote diagnostics. In this tutorial, you will:
- Launch the gateway with demo nodes
- Discover areas and components
- Read sensor data via REST API
- Call services and manage parameters
- Monitor and clear faults
- ros2_medkit installed (see :doc:`installation`)
- Terminal with ROS 2 environment sourced
curlor a REST client (Postman recommended)
Open three terminals. In each, source your workspace:
source ~/ros2_medkit/install/setup.bashTerminal 1 - Start the gateway:
ros2 launch ros2_medkit_gateway gateway.launch.py \
tls_enabled:=false \
jwt_secret:=change-me-to-at-least-32-characters-long \
auth_clients:=demo:demo-secret:adminYou should see:
[gateway_node]: REST server starting on http://127.0.0.1:8080
[gateway_node]: REST server started successfully
Terminal 2 - Start demo nodes:
ros2 launch ros2_medkit_integration_tests demo_nodes.launch.pyThis launches automotive demo nodes from the integration tests package that we'll use to explore the API.
| Node Name | Entity ID | Namespace | Description |
|---|---|---|---|
| temp_sensor | powertrain_engine_component.temp_sensor | /powertrain/engine | Engine temperature sensor |
| rpm_sensor | powertrain_engine_component.rpm_sensor | /powertrain/engine | Engine RPM sensor |
| calibration | powertrain_engine_component.calibration | /powertrain/engine | Calibration service (sync) |
| long_calibration | powertrain_engine_component.long_calibration | /powertrain/engine | Long calibration action (async) |
| pressure_sensor | chassis_brakes_component.pressure_sensor | /chassis/brakes | Brake pressure sensor |
| actuator | chassis_brakes_component.actuator | /chassis/brakes | Brake actuator |
| status_sensor | body_door_front_left_component.status_sensor | /body/door/front_left | Door status sensor |
| controller | body_lights_component.controller | /body/lights | Light controller |
| lidar_sensor | perception_lidar_component.lidar_sensor | /perception/lidar | LiDAR sensor with faults |
Note
In runtime-only discovery mode, entity IDs are derived from the namespace path.
Use the /components endpoint to discover actual component IDs.
Terminal 3 - (Optional) Start fault manager:
mkdir -p $HOME/.ros2_medkit
ros2 run ros2_medkit_fault_manager fault_manager_node --ros-args -p database_path:=$HOME/.ros2_medkit/faults.dbRequired if you want to test the Faults API.
Note
The ~/.ros2_medkit/ directory must exist before starting the fault manager.
SQLite will create the database file automatically.
Important
The gateway ships closed: auth.enabled is true and
require_auth_for is all, so every route below needs a credential and
the gateway will not start without a signing secret. That is deliberate - a
gateway that booted open is one nobody notices. The two arguments above are
a throwaway development credential; a real deployment injects them from its
own secret store.
Get a token once and reuse it for every command on this page:
TOKEN=$(curl -s http://localhost:8080/api/v1/auth/authorize \
-H 'Content-Type: application/json' \
-d '{"grant_type":"client_credentials","client_id":"demo","client_secret":"demo-secret"}' \
| jq -r .access_token)tls_enabled:=false is what keeps the rest of this page on http://.
TLS is on in the shipped config and the gateway will not start without a
certificate, so a first run either turns it off, as here, or supplies one:
run scripts/generate_dev_certs.sh ./certs and pass
cert_file:=./certs/cert.pem key_file:=./certs/key.pem. Turn it off only
on a host nothing else can reach. See :doc:`tutorials/https` for a real
certificate.
POST /api/v1/auth/authorize takes the client_credentials grant;
/auth/token is the refresh endpoint and takes refresh_token.
GET /api/v1/health is the other route that stays open, so a container
supervisor with no credential can still tell the process is alive.
To run without authentication - only on a host nothing else can reach -
pass auth_enabled:=false.
✅ Checkpoint
At this point you should have:
- Gateway running on http://localhost:8080
- Demo nodes publishing data
- Terminal 1 showing:
[gateway_node]: REST server started successfully
The gateway exposes all endpoints under /api/v1. Let's explore!
Check gateway health:
curl http://localhost:8080/api/v1/healthGet gateway capabilities:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/Response shows available endpoints and version info.
✅ Checkpoint
Health check should return: {"status": "healthy", "timestamp": ...}
If you see connection refused, verify gateway is running.
ros2_medkit organizes ROS 2 nodes into a SOVD-aligned entity hierarchy:
- Areas - Logical/physical domains (e.g.,
/powertrain,/chassis) - Components - Hardware or virtual units that group Apps
- Apps - Individual ROS 2 nodes
- Functions - Cross-cutting capabilities (requires manifest mode)
Note
Discovery Modes
- Runtime-only (default): ROS 2 nodes are exposed as Apps. A single host-level Component is created from system info. Namespace prefixes create Functions that group related Apps.
- Hybrid: Manifest defines Areas/Components/Apps/Functions, runtime links them to live ROS 2 nodes.
- Manifest-only: Only manifest-declared entities are exposed.
Areas are created from manifest only - they are never auto-generated in
runtime mode. Omit the areas: section in the manifest for a flat tree.
See :doc:`tutorials/manifest-discovery` for details on manifest mode.
Important
If you are upgrading from a previous version, the entity model has changed significantly. Synthetic Areas and per-namespace Components are no longer created. See :ref:`Breaking Changes <aggregation-breaking-changes>` for details and migration guidance.
In this tutorial, we use runtime-only mode with demo_nodes.launch.py.
List all functions:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/functionsWith demo_nodes.launch.py, you'll see Functions like powertrain, chassis, and body
(created from the first namespace segment).
List all components:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/componentsIn runtime mode, you'll see a single host-level Component.
List all areas:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/areasIn runtime mode, this returns an empty list. Areas require a manifest definition (see :doc:`tutorials/manifest-discovery`).
The data endpoints let you read topic data from apps.
Read all data from an app:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/dataResponse structure (showing one topic):
{
"items": [
{
"category": "currentData",
"id": "/powertrain/engine/temperature",
"name": "/powertrain/engine/temperature",
"x-medkit": {
"ros2": {
"direction": "publish",
"topic": "/powertrain/engine/temperature",
"type": "sensor_msgs/msg/Temperature"
},
"type_info": {
"default_value": {
"header": {},
"temperature": 0,
"variance": 0
},
"schema": {
"properties": {
"header": {},
"temperature": {"type": "number"},
"variance": {"type": "number"}
},
"type": "object"
}
}
}
}
],
"x-medkit": {
"entity_id": "temp_sensor",
"total_count": 3
}
}Each data item includes:
category: Type of data (currentData)idandname: ROS 2 topic pathx-medkit.ros2: Topic metadata (direction, type)x-medkit.type_info.schema: JSON Schema for the message typex-medkit.type_info.default_value: Default message structure
Read a specific topic:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/data/powertrain%2Fengine%2FtemperatureResponse with live data:
{
"data": {
"header": {
"frame_id": "engine",
"stamp": {"sec": 1769955040, "nanosec": 286555163}
},
"temperature": 93.5,
"variance": 0.5
},
"id": "/powertrain/engine/temperature",
"x-medkit": {
"entity_id": "temp_sensor",
"timestamp": 1769955039964403368,
"ros2": {
"topic": "/powertrain/engine/temperature",
"type": "sensor_msgs/msg/Temperature"
},
"publisher_count": 1,
"subscriber_count": 0,
"status": "data"
}
}Notice:
data: The actual message content from ROS 2 topicx-medkit.timestamp: Gateway capture time (nanoseconds since epoch)publisher_count/subscriber_count: Number of publishers/subscribers on this topic
Note
Topic paths use URL encoding: / becomes %2F
✅ Checkpoint
You should see:
- Functions like
powertrain,chassis,body(from namespace segments) - A single host-level Component (from system info)
- Live topic data with actual sensor readings
The operations endpoints let you call ROS 2 services and actions.
List available operations:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/calibration/operationsCall a service (synchronous execution):
Services return immediately with status 200 OK:
curl -X POST http://localhost:8080/api/v1/apps/calibration/operations/calibrate/executions \
-H "Content-Type: application/json" \
-d '{}'Response (200 OK):
{
"parameters": {
"success": true,
"message": "Engine calibrated successfully (count: 1)"
}
}The parameters field contains the service response data directly.
Send an action goal (asynchronous execution):
Actions return 202 Accepted immediately with an execution ID for polling:
curl -X POST http://localhost:8080/api/v1/apps/long_calibration/operations/long_calibration/executions \
-H "Content-Type: application/json" \
-d '{"parameters": {"order": 5}}'Response (202 Accepted):
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "running"
}Poll action status:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/long_calibration/operations/long_calibration/executions/a1b2c3d4-e5f6-7890-abcd-ef1234567890Cancel a running action:
curl -X DELETE http://localhost:8080/api/v1/apps/long_calibration/operations/long_calibration/executions/a1b2c3d4-e5f6-7890-abcd-ef1234567890Returns 204 No Content on success.
The configurations endpoints expose ROS 2 parameters.
List all parameters:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/configurationsGet a specific parameter:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rateSet a parameter value:
curl -X PUT http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rate \
-H "Content-Type: application/json" \
-d '{"value": 5.0}'Reset to default:
curl -X DELETE http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rateNote
Requires ros2_medkit_fault_manager to be running.
List all system faults:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/faultsList faults for a specific component:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/apps/lidar_sensor/faultsClear a fault:
curl -X DELETE http://localhost:8080/api/v1/apps/lidar_sensor/faults/LIDAR_CALIBRATION_REQUIRED✅ Checkpoint
At this point you've successfully:
- Discovered the ROS 2 system structure
- Read sensor data via REST API
- Called services and managed actions
- Managed node parameters
- Queried and cleared faults
🎉 You're ready to explore the web UI and advanced features!
A companion web UI is available for visual entity browsing:
docker pull ghcr.io/selfpatch/ros2_medkit_web_ui:latest
docker run -p 3000:80 ghcr.io/selfpatch/ros2_medkit_web_ui:latestOpen http://localhost:3000 and connect to the gateway at http://localhost:8080.
See :doc:`tutorials/web-ui` for more details.
Connect your LLM to the gateway using ros2_medkit_mcp:
Option 1: Docker (recommended)
# Pull and run HTTP server on port 8765
docker run -p 8765:8765 \
-e ROS2_MEDKIT_BASE_URL=http://host.docker.internal:8080/api/v1 \
ghcr.io/selfpatch/ros2_medkit_mcp:latest
# Or run with stdio transport
docker run -i \
-e ROS2_MEDKIT_BASE_URL=http://host.docker.internal:8080/api/v1 \
ghcr.io/selfpatch/ros2_medkit_mcp:latest stdioOption 2: Poetry (for development)
git clone https://github.com/selfpatch/ros2_medkit_mcp.git
cd ros2_medkit_mcp
poetry install
poetry run ros2-medkit-mcp-stdioSee :doc:`tutorials/mcp-server` for Claude Desktop and VS Code integration.
For interactive API testing, import our Postman collection:
- Import
postman/collections/ros2-medkit-gateway.postman_collection.json - Import
postman/environments/local.postman_environment.json - Select "ROS 2 Medkit Gateway - Local" environment
See postman/README.md for detailed instructions.
Configuration:
- :doc:`config/server` - Server, CORS, and TLS settings
- :doc:`config/discovery-options` - Discovery mode configuration
Tutorials:
- :doc:`tutorials/authentication` - Enable JWT authentication
- :doc:`tutorials/https` - Configure TLS/HTTPS
- :doc:`tutorials/manifest-discovery` - Use manifests for stable entity IDs
- :doc:`tutorials/docker` - Deploy with Docker
Companion Projects:
- :doc:`tutorials/web-ui` - Visual entity browser
- :doc:`tutorials/mcp-server` - LLM integration via MCP
Reference:
- :doc:`api/rest` - Complete REST API reference
- :doc:`design/ros2_medkit_gateway/index` - Architecture deep-dive
