You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/08/29 12:08:52 UTC

(camel) 01/01: CAMEL-24564: camel-tooling-model - Generate a static OpenAPI spec for all dev consoles and publish it in the camel-catalog

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch feature/CAMEL-24564-devconsole-openapi-catalog
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 4ba2803df33ac3f9b5ed5ada8ea94aa6a5a8ddd6
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Aug 29 14:08:34 2026 +0200

    CAMEL-24564: camel-tooling-model - Generate a static OpenAPI spec for all dev consoles and publish it in the camel-catalog
    
    Extract the OpenAPI path/operation-building logic out of ApiDevConsole into a
    new shared DevConsoleOpenApiHelper in camel-tooling-model, operating on the
    existing DevConsoleModel/DevConsoleOptionModel typed beans (adding the
    readOnly flag from CAMEL-24514 to that model and to JsonMapper's
    generate/serialize methods, which it was previously missing).
    
    ApiDevConsole now builds its live spec by parsing each console's catalog
    schema via JsonMapper.generateDevConsoleModel() and delegating to the shared
    helper, instead of hand-rolling JSON navigation - behavior-preserving.
    
    PrepareCatalogMojo's executeDevConsoles() already parses every console's
    catalog JSON into a DevConsoleModel while aggregating them into
    camel-catalog; it now also builds a single consolidated
    dev-consoles-openapi.json from those same models via the shared helper, with
    zero additional parsing and no CamelContext dependency. Exposed via a new
    CamelCatalog.devConsolesOpenApiSpec() method, mirroring the existing
    mainJsonSchema()/jbangJsonSchema() raw-resource pattern.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
    Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
 .../apache/camel/catalog/dev-consoles-openapi.json | 2489 ++++++++++++++++++++
 .../org/apache/camel/catalog/CamelCatalog.java     |    5 +
 .../apache/camel/catalog/DefaultCamelCatalog.java  |    5 +
 .../org/apache/camel/catalog/CamelCatalogTest.java |   25 +
 core/camel-console/pom.xml                         |    5 +
 .../apache/camel/impl/console/ApiDevConsole.java   |  183 +-
 .../camel/tooling/model/DevConsoleModel.java       |    9 +
 .../tooling/model/DevConsoleOpenApiHelper.java     |  160 ++
 .../org/apache/camel/tooling/model/JsonMapper.java |    2 +
 .../tooling/model/DevConsoleOpenApiHelperTest.java |  126 +
 .../camel/maven/packaging/PrepareCatalogMojo.java  |   12 +
 11 files changed, 2858 insertions(+), 163 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
new file mode 100644
index 000000000000..6846a963fad3
--- /dev/null
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
@@ -0,0 +1,2489 @@
+{
+	"openapi": "3.0.3",
+	"info": {
+		"title": "Camel Dev Console API",
+		"version": "4.23.0-SNAPSHOT"
+	},
+	"paths": {
+		"\/q\/dev\/activity": {
+			"get": {
+				"summary": "Camel Activity",
+				"description": "Recent completed exchange activity",
+				"operationId": "activity",
+				"responses": {
+					"200": {
+						"description": "Camel Activity output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/api": {
+			"get": {
+				"summary": "API",
+				"description": "OpenAPI specification for the dev console API",
+				"operationId": "api",
+				"responses": {
+					"200": {
+						"description": "API output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/aws-secrets": {
+			"get": {
+				"summary": "AWS Secrets",
+				"description": "AWS Secrets Manager",
+				"operationId": "aws-secrets",
+				"responses": {
+					"200": {
+						"description": "AWS Secrets output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/aws2-s3": {
+			"get": {
+				"summary": "AWS S3",
+				"description": "AWS S3 Consumer",
+				"operationId": "aws2-s3",
+				"responses": {
+					"200": {
+						"description": "AWS S3 output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/azure-secrets": {
+			"get": {
+				"summary": "Azure Key Vault Secrets",
+				"description": "Azure Key Vault Secret Manager",
+				"operationId": "azure-secrets",
+				"responses": {
+					"200": {
+						"description": "Azure Key Vault Secrets output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/bean": {
+			"get": {
+				"summary": "Bean",
+				"description": "Displays Java beans from the registry",
+				"operationId": "bean",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the beans matching by name",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "internal",
+						"in": "query",
+						"description": "Whether to include internal Camel beans",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "nulls",
+						"in": "query",
+						"description": "Whether to include null values",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "properties",
+						"in": "query",
+						"description": "Whether to include bean properties",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Bean output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/bean-model": {
+			"get": {
+				"summary": "Bean Model",
+				"description": "Displays beans from the DSL model",
+				"operationId": "bean-model",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the beans matching by name",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "nulls",
+						"in": "query",
+						"description": "Whether to include null values",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "properties",
+						"in": "query",
+						"description": "Whether to include bean properties",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Bean Model output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/blocked": {
+			"get": {
+				"summary": "Blocked Exchanges",
+				"description": "Display blocked exchanges",
+				"operationId": "blocked",
+				"responses": {
+					"200": {
+						"description": "Blocked Exchanges output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/browse": {
+			"get": {
+				"summary": "Browse",
+				"description": "Browse pending messages on Camel components",
+				"operationId": "browse",
+				"parameters": [
+					{
+						"name": "bodyMaxChars",
+						"in": "query",
+						"description": "Maximum size of the message body to include in the dump",
+						"required": false,
+						"schema": {
+							"type": "integer",
+							"default": 32768
+						}
+					},
+					{
+						"name": "dump",
+						"in": "query",
+						"description": "Whether to include message dumps",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the endpoints matching by route id, endpoint url",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "freshSize",
+						"in": "query",
+						"description": "Whether to calculate fresh queue size (can cause performance overhead)",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					},
+					{
+						"name": "includeBody",
+						"in": "query",
+						"description": "Whether to include message body in dumps",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of entries per endpoint",
+						"required": false,
+						"schema": {
+							"type": "integer",
+							"default": 100
+						}
+					},
+					{
+						"name": "tail",
+						"in": "query",
+						"description": "To receive N last messages from the tail",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Browse output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/circuit-breaker": {
+			"get": {
+				"summary": "Circuit Breaker",
+				"description": "Display circuit breaker information",
+				"operationId": "circuit-breaker",
+				"responses": {
+					"200": {
+						"description": "Circuit Breaker output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/consumer": {
+			"get": {
+				"summary": "Consumers",
+				"description": "Display information about Camel consumers",
+				"operationId": "consumer",
+				"responses": {
+					"200": {
+						"description": "Consumers output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/context": {
+			"get": {
+				"summary": "CamelContext",
+				"description": "Overall information about the CamelContext",
+				"operationId": "context",
+				"responses": {
+					"200": {
+						"description": "CamelContext output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/datasource": {
+			"get": {
+				"summary": "DataSource",
+				"description": "Displays DataSource connection pool metrics",
+				"operationId": "datasource",
+				"responses": {
+					"200": {
+						"description": "DataSource output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/debug": {
+			"post": {
+				"summary": "Debug",
+				"description": "Camel route debugger",
+				"operationId": "debug",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"breakpoint": {
+										"type": "string",
+										"description": "The breakpoint node id"
+									},
+									"codeLimit": {
+										"type": "integer",
+										"default": 5,
+										"description": "Number of source code lines around the breakpoint to include"
+									},
+									"command": {
+										"type": "string",
+										"description": "Action command to execute on the debugger"
+									},
+									"history": {
+										"type": "boolean",
+										"default": true,
+										"description": "Whether to include message history"
+									},
+									"position": {
+										"type": "integer",
+										"description": "The position to step to"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Debug output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/endpoint": {
+			"get": {
+				"summary": "Endpoints",
+				"description": "Endpoint Registry information",
+				"operationId": "endpoint",
+				"responses": {
+					"200": {
+						"description": "Endpoints output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/errors": {
+			"get": {
+				"summary": "Error Registry",
+				"description": "Display captured routing errors",
+				"operationId": "errors",
+				"parameters": [
+					{
+						"name": "ago",
+						"in": "query",
+						"description": "Filter by time window as duration string (e.g. 60s, 5m, 1h). Only entries within this window are included.",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "exception",
+						"in": "query",
+						"description": "Filter by exception type (case-insensitive substring match)",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "handled",
+						"in": "query",
+						"description": "Filter by handled status",
+						"required": false,
+						"schema": {
+							"type": "boolean"
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of entries displayed",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					},
+					{
+						"name": "routeId",
+						"in": "query",
+						"description": "Filter by route id",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "stackTrace",
+						"in": "query",
+						"description": "Whether to include stack traces",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Error Registry output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/eval-language": {
+			"post": {
+				"summary": "Evaluate Language",
+				"description": "Evaluate Language and display result",
+				"operationId": "eval-language",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"body": {
+										"type": "string",
+										"description": "Optional message body"
+									},
+									"headers": {
+										"type": "string",
+										"description": "Optional message headers"
+									},
+									"language": {
+										"type": "string",
+										"default": "simple",
+										"description": "The language to use"
+									},
+									"predicate": {
+										"type": "boolean",
+										"default": false,
+										"description": "Whether to execute as predicate (use expression by default)"
+									},
+									"template": {
+										"type": "string",
+										"description": "Template to use for executing simple language function"
+									},
+									"variables": {
+										"type": "string",
+										"description": "Optional exchange variables"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Evaluate Language output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/event": {
+			"get": {
+				"summary": "Camel Events",
+				"description": "The most recent Camel events",
+				"operationId": "event",
+				"responses": {
+					"200": {
+						"description": "Camel Events output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/fault-tolerance": {
+			"get": {
+				"summary": "MicroProfile Circuit Breaker",
+				"description": "Display circuit breaker information",
+				"operationId": "fault-tolerance",
+				"responses": {
+					"200": {
+						"description": "MicroProfile Circuit Breaker output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/gc": {
+			"get": {
+				"summary": "Garbage Collector",
+				"description": "Displays Garbage Collector information",
+				"operationId": "gc",
+				"responses": {
+					"200": {
+						"description": "Garbage Collector output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/gcp-secrets": {
+			"get": {
+				"summary": "GCP Secrets",
+				"description": "GCP Secret Manager",
+				"operationId": "gcp-secrets",
+				"responses": {
+					"200": {
+						"description": "GCP Secrets output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/groovy": {
+			"get": {
+				"summary": "Groovy",
+				"description": "Groovy Language",
+				"operationId": "groovy",
+				"responses": {
+					"200": {
+						"description": "Groovy output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/hashicorp-secrets": {
+			"get": {
+				"summary": "HashiCorp Secrets",
+				"description": "HashiCorp Vault Secrets",
+				"operationId": "hashicorp-secrets",
+				"responses": {
+					"200": {
+						"description": "HashiCorp Secrets output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/health": {
+			"get": {
+				"summary": "Health Check",
+				"description": "Health Check Status",
+				"operationId": "health",
+				"parameters": [
+					{
+						"name": "exposureLevel",
+						"in": "query",
+						"description": "Exposure level for health check details",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"default": "default",
+							"enum": [
+								"default",
+								"oneline",
+								"full"
+							]
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Health Check output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/heap-dump": {
+			"post": {
+				"summary": "Heap Dump",
+				"description": "Write a heap dump (.hprof) file for deep memory analysis",
+				"operationId": "heap-dump",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"live": {
+										"type": "boolean",
+										"default": true,
+										"description": "Whether to dump only live objects (default true)"
+									},
+									"name": {
+										"type": "string",
+										"description": "File name for the heap dump (without .hprof extension)"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Heap Dump output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/heap-histogram": {
+			"get": {
+				"summary": "Heap Histogram",
+				"description": "Displays class-level heap memory usage",
+				"operationId": "heap-histogram",
+				"parameters": [
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of classes displayed",
+						"required": false,
+						"schema": {
+							"type": "integer",
+							"default": 100
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Heap Histogram output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/ibm-secrets": {
+			"get": {
+				"summary": "IBM Secrets",
+				"description": "IBM Secrets Manager",
+				"operationId": "ibm-secrets",
+				"responses": {
+					"200": {
+						"description": "IBM Secrets output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/inflight": {
+			"get": {
+				"summary": "Inflight Exchanges",
+				"description": "Display inflight exchanges",
+				"operationId": "inflight",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the routes matching by route id, route uri",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of entries displayed",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Inflight Exchanges output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/internal-tasks": {
+			"get": {
+				"summary": "Internal Tasks",
+				"description": "Display information about internal tasks",
+				"operationId": "internal-tasks",
+				"responses": {
+					"200": {
+						"description": "Internal Tasks output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/java-security": {
+			"get": {
+				"summary": "Java Security",
+				"description": "Displays Java Security (JSSE) information",
+				"operationId": "java-security",
+				"responses": {
+					"200": {
+						"description": "Java Security output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/jfr": {
+			"post": {
+				"summary": "JFR Runtime Instrumentation",
+				"description": "Status and live control of camel-jfr runtime instrumentation",
+				"operationId": "jfr",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"command": {
+										"type": "string",
+										"default": "status",
+										"enum": [
+											"status",
+											"enable",
+											"disable",
+											"jfc",
+											"snapshot"
+										],
+										"description": "Command to perform"
+									},
+									"disable": {
+										"type": "string",
+										"description": "Comma separated list of events to disable in the generated jfc settings overlay"
+									},
+									"event": {
+										"type": "string",
+										"default": "all",
+										"enum": [
+											"all",
+											"route",
+											"processor",
+											"exchange",
+											"send",
+											"failed",
+											"redelivery"
+										],
+										"description": "The runtime event to enable or disable, or all for every event"
+									},
+									"limit": {
+										"type": "integer",
+										"default": 50,
+										"description": "Maximum number of failure and redelivery entries to return in a snapshot"
+									},
+									"routeId": {
+										"type": "string",
+										"description": "Filter snapshot results to the given route id"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "JFR Runtime Instrumentation output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/jfr-memory-leak": {
+			"post": {
+				"summary": "JFR Memory Leak",
+				"description": "JFR-based old object sampling for memory leak diagnosis",
+				"operationId": "jfr-memory-leak",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"command": {
+										"type": "string",
+										"default": "status",
+										"enum": [
+											"start",
+											"stop",
+											"status",
+											"query",
+											"compare"
+										],
+										"description": "Command to execute"
+									},
+									"duration": {
+										"type": "integer",
+										"default": 0,
+										"description": "Recording duration in seconds (0 means manual stop)"
+									},
+									"limit": {
+										"type": "integer",
+										"default": 100,
+										"description": "Limits the number of entries displayed"
+									},
+									"minSize": {
+										"type": "integer",
+										"default": 0,
+										"description": "Minimum object size in bytes to include in results"
+									},
+									"stacktrace": {
+										"type": "boolean",
+										"default": false,
+										"description": "Whether to include stack traces in the output"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "JFR Memory Leak output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/jvm": {
+			"get": {
+				"summary": "JVM",
+				"description": "Displays JVM information",
+				"operationId": "jvm",
+				"responses": {
+					"200": {
+						"description": "JVM output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/kafka": {
+			"get": {
+				"summary": "Kafka",
+				"description": "Apache Kafka",
+				"operationId": "kafka",
+				"parameters": [
+					{
+						"name": "committed",
+						"in": "query",
+						"description": "Whether to include committed offset (sync operation to Kafka broker)",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Kafka output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/kubernetes-configmaps": {
+			"get": {
+				"summary": "Kubernetes Config Maps",
+				"description": "Kubernetes Cluster Config Maps",
+				"operationId": "kubernetes-configmaps",
+				"responses": {
+					"200": {
+						"description": "Kubernetes Config Maps output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/kubernetes-secrets": {
+			"get": {
+				"summary": "Kubernetes Secrets",
+				"description": "Kubernetes Cluster Secrets",
+				"operationId": "kubernetes-secrets",
+				"responses": {
+					"200": {
+						"description": "Kubernetes Secrets output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/log": {
+			"get": {
+				"summary": "Log",
+				"description": "Logging framework",
+				"operationId": "log",
+				"responses": {
+					"200": {
+						"description": "Log output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/main-configuration": {
+			"get": {
+				"summary": "Main Configuration",
+				"description": "Display Camel startup configuration",
+				"operationId": "main-configuration",
+				"responses": {
+					"200": {
+						"description": "Main Configuration output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/main-http-server": {
+			"get": {
+				"summary": "Main HTTP Server",
+				"description": "Camel Main HTTP Server",
+				"operationId": "main-http-server",
+				"responses": {
+					"200": {
+						"description": "Main HTTP Server output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/memory": {
+			"get": {
+				"summary": "JVM Memory",
+				"description": "Displays JVM memory information",
+				"operationId": "memory",
+				"responses": {
+					"200": {
+						"description": "JVM Memory output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/message-history": {
+			"get": {
+				"summary": "Message History",
+				"description": "History of latest completed exchange",
+				"operationId": "message-history",
+				"parameters": [
+					{
+						"name": "codeLimit",
+						"in": "query",
+						"description": "Number of source code lines around the location to include",
+						"required": false,
+						"schema": {
+							"type": "integer",
+							"default": 5
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Message History output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/micrometer": {
+			"get": {
+				"summary": "Micrometer",
+				"description": "Display runtime metrics",
+				"operationId": "micrometer",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters matching metrics by name",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "tags",
+						"in": "query",
+						"description": "Whether to include tags",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Micrometer output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/opentelemetry": {
+			"get": {
+				"summary": "OpenTelemetry Spans",
+				"description": "OpenTelemetry span data captured in dev mode",
+				"operationId": "opentelemetry",
+				"parameters": [
+					{
+						"name": "dump",
+						"in": "query",
+						"description": "Whether to dump span data",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"enum": [
+								"true",
+								"false"
+							]
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of spans dumped",
+						"required": false,
+						"schema": {
+							"type": "integer",
+							"default": 100
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "OpenTelemetry Spans output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/platform-http": {
+			"get": {
+				"summary": "Platform HTTP",
+				"description": "Embedded HTTP Server",
+				"operationId": "platform-http",
+				"responses": {
+					"200": {
+						"description": "Platform HTTP output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/processor": {
+			"post": {
+				"summary": "Processor",
+				"description": "Processor information",
+				"operationId": "processor",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"action": {
+										"type": "string",
+										"enum": [
+											"start",
+											"stop",
+											"enable",
+											"disable"
+										],
+										"description": "Action to perform such as start,stop,enable,disable on one or more processors"
+									},
+									"filter": {
+										"type": "string",
+										"description": "Filters the processors matching by processor id, route id, or route group, and source location"
+									},
+									"limit": {
+										"type": "integer",
+										"description": "Limits the number of entries displayed"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Processor output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/processor-detail": {
+			"get": {
+				"summary": "Processor Detail",
+				"description": "Show configured options for all processors in a route",
+				"operationId": "processor-detail",
+				"parameters": [
+					{
+						"name": "routeId",
+						"in": "query",
+						"description": "The route id to get processor details for (use * for all routes)",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Processor Detail output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/producer": {
+			"get": {
+				"summary": "Producers",
+				"description": "Display information about Camel producers",
+				"operationId": "producer",
+				"responses": {
+					"200": {
+						"description": "Producers output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/properties": {
+			"get": {
+				"summary": "Properties",
+				"description": "Displays the properties loaded by Camel",
+				"operationId": "properties",
+				"responses": {
+					"200": {
+						"description": "Properties output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/quartz": {
+			"get": {
+				"summary": "Quartz",
+				"description": "Quartz Scheduler",
+				"operationId": "quartz",
+				"responses": {
+					"200": {
+						"description": "Quartz output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/receive": {
+			"post": {
+				"summary": "Camel Receive",
+				"description": "Consume messages from endpoints",
+				"operationId": "receive",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"dump": {
+										"type": "string",
+										"enum": [
+											"true",
+											"false"
+										],
+										"description": "Whether to dump received messages"
+									},
+									"enabled": {
+										"type": "string",
+										"enum": [
+											"true",
+											"false"
+										],
+										"description": "Whether to enable or disable receive mode"
+									},
+									"endpoint": {
+										"type": "string",
+										"description": "Endpoint for where to receive messages (can also refer to a route id, endpoint pattern)"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Camel Receive output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/reload": {
+			"post": {
+				"summary": "Reload",
+				"description": "Console for reloading running Camel",
+				"operationId": "reload",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"reload": {
+										"type": "boolean",
+										"default": false,
+										"description": "Option to trigger reloading"
+									},
+									"wait": {
+										"type": "boolean",
+										"default": false,
+										"description": "Option to wait for reloading to complete"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Reload output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/resilience4j": {
+			"get": {
+				"summary": "Resilience Circuit Breaker",
+				"description": "Display circuit breaker information",
+				"operationId": "resilience4j",
+				"responses": {
+					"200": {
+						"description": "Resilience Circuit Breaker output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/rest": {
+			"get": {
+				"summary": "Rest",
+				"description": "Rest DSL Registry information",
+				"operationId": "rest",
+				"responses": {
+					"200": {
+						"description": "Rest output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/rest-spec": {
+			"get": {
+				"summary": "Rest Spec",
+				"description": "OpenAPI specification content for contract-first REST services",
+				"operationId": "rest-spec",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters specifications matching the given URI pattern",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Rest Spec output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/route": {
+			"post": {
+				"summary": "Route",
+				"description": "Route information",
+				"operationId": "route",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"action": {
+										"type": "string",
+										"enum": [
+											"start",
+											"stop",
+											"suspend",
+											"resume"
+										],
+										"description": "Action to perform such as start,stop,suspend,resume on one or more routes"
+									},
+									"filter": {
+										"type": "string",
+										"description": "Filters the routes matching by route id, route uri, or route group, and source location"
+									},
+									"limit": {
+										"type": "integer",
+										"description": "Limits the number of entries displayed"
+									},
+									"processors": {
+										"type": "boolean",
+										"default": false,
+										"description": "Whether to include processors"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Route output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/route-controller": {
+			"get": {
+				"summary": "Route Controller",
+				"description": "Route controller information",
+				"operationId": "route-controller",
+				"parameters": [
+					{
+						"name": "error",
+						"in": "query",
+						"description": "Whether to include error details",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "stacktrace",
+						"in": "query",
+						"description": "Whether to include stack traces",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Route Controller output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/route-diagram": {
+			"get": {
+				"summary": "Route Diagram",
+				"description": "Visual route diagrams",
+				"operationId": "route-diagram",
+				"parameters": [
+					{
+						"name": "autoRefresh",
+						"in": "query",
+						"description": "Whether to auto-refresh page every 5 seconds",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "external",
+						"in": "query",
+						"description": "Whether to include external systems (kafka, http, etc.) in topology mode",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the routes matching by route id, route uri, and source location",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"default": "*"
+						}
+					},
+					{
+						"name": "fontSize",
+						"in": "query",
+						"description": "The size of the font",
+						"required": false,
+						"schema": {
+							"type": "integer",
+							"default": 12
+						}
+					},
+					{
+						"name": "format",
+						"in": "query",
+						"description": "Output format: html (interactive web component), png (inline image), text or ascii (ASCII art), unicode (box-drawing characters)",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"default": "html",
+							"enum": [
+								"html",
+								"png",
+								"text",
+								"ascii",
+								"unicode"
+							]
+						}
+					},
+					{
+						"name": "metric",
+						"in": "query",
+						"description": "Whether to include live metric counters",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					},
+					{
+						"name": "mode",
+						"in": "query",
+						"description": "Diagram mode: route or topology",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"default": "route",
+							"enum": [
+								"route",
+								"topology"
+							]
+						}
+					},
+					{
+						"name": "nodeLabel",
+						"in": "query",
+						"description": "Node label mode",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"default": "code",
+							"enum": [
+								"code",
+								"description",
+								"both"
+							]
+						}
+					},
+					{
+						"name": "nodeWidth",
+						"in": "query",
+						"description": "The node width in pixels",
+						"required": false,
+						"schema": {
+							"type": "integer",
+							"default": 180
+						}
+					},
+					{
+						"name": "theme",
+						"in": "query",
+						"description": "Theme to use: dark, light, transparent, ascii, or unicode",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"default": "transparent",
+							"enum": [
+								"dark",
+								"light",
+								"transparent",
+								"ascii",
+								"unicode"
+							]
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Route Diagram output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/route-dump": {
+			"get": {
+				"summary": "Route Dump",
+				"description": "Dump route in XML, YAML, or Java DSL format",
+				"operationId": "route-dump",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the routes matching by route id, route uri, and source location",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "format",
+						"in": "query",
+						"description": "To output in either xml, yaml, or java format",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"enum": [
+								"xml",
+								"yaml",
+								"java"
+							]
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of entries displayed",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					},
+					{
+						"name": "uriAsParameters",
+						"in": "query",
+						"description": "Whether to expand URIs into separated key\/value parameters",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Route Dump output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/route-group": {
+			"post": {
+				"summary": "Route Group",
+				"description": "Route Group information",
+				"operationId": "route-group",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"action": {
+										"type": "string",
+										"enum": [
+											"start",
+											"stop"
+										],
+										"description": "Action to perform such as start or stop"
+									},
+									"filter": {
+										"type": "string",
+										"description": "Filters the route groups matching by group id"
+									},
+									"limit": {
+										"type": "integer",
+										"description": "Limits the number of entries displayed"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Route Group output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/route-structure": {
+			"get": {
+				"summary": "Route Structure",
+				"description": "Dump route structure",
+				"operationId": "route-structure",
+				"parameters": [
+					{
+						"name": "brief",
+						"in": "query",
+						"description": "Whether to dump in brief mode (only overall structure, and no detailed options or expressions)",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					},
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the routes matching by route id, route uri, and source location",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of entries displayed",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					},
+					{
+						"name": "metric",
+						"in": "query",
+						"description": "Whether to include metrics such as number of messages processed (from JMX)",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Route Structure output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/route-topology": {
+			"get": {
+				"summary": "Route Topology",
+				"description": "Route topology showing inter-route connections",
+				"operationId": "route-topology",
+				"parameters": [
+					{
+						"name": "external",
+						"in": "query",
+						"description": "Whether to include external systems (databases, messaging brokers, etc.) as nodes",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					},
+					{
+						"name": "metric",
+						"in": "query",
+						"description": "Whether to include live metrics (message counts) on nodes and edges",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					},
+					{
+						"name": "routes",
+						"in": "query",
+						"description": "Whether to include route structure data in the response",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Route Topology output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/send": {
+			"post": {
+				"summary": "Camel Send",
+				"description": "Send messages to endpoints",
+				"operationId": "send",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"body": {
+										"type": "string",
+										"description": "The message body to send. Can refer to files using file: prefix"
+									},
+									"bodyMaxChars": {
+										"type": "integer",
+										"default": 32768,
+										"description": "Maximum size of the message body to include in the dump"
+									},
+									"endpoint": {
+										"type": "string",
+										"description": "Endpoint for where to send messages (can also refer to a route id, endpoint pattern)"
+									},
+									"exchangePattern": {
+										"type": "string",
+										"enum": [
+											"InOnly",
+											"InOut"
+										],
+										"description": "Exchange pattern when sending"
+									},
+									"poll": {
+										"type": "boolean",
+										"description": "Whether to poll message from the endpoint instead of sending"
+									},
+									"pollTimeout": {
+										"type": "integer",
+										"default": 20000,
+										"description": "Timeout when using poll mode"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Camel Send output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/service": {
+			"get": {
+				"summary": "Services",
+				"description": "Services used for network communication with clients",
+				"operationId": "service",
+				"responses": {
+					"200": {
+						"description": "Services output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/sftp": {
+			"get": {
+				"summary": "SFTP",
+				"description": "Secure FTP using JSCH",
+				"operationId": "sftp",
+				"responses": {
+					"200": {
+						"description": "SFTP output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/simple-language": {
+			"get": {
+				"summary": "Simple Language",
+				"description": "Display simple language details",
+				"operationId": "simple-language",
+				"responses": {
+					"200": {
+						"description": "Simple Language output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/source": {
+			"get": {
+				"summary": "Source",
+				"description": "Dump route source code",
+				"operationId": "source",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the routes matching by route id, route uri, and source location",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of entries displayed",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Source output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/sql-query": {
+			"post": {
+				"summary": "SQL Query",
+				"description": "Execute SQL queries on DataSource beans",
+				"operationId": "sql-query",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"actionType": {
+										"type": "string",
+										"default": "query",
+										"enum": [
+											"query",
+											"update-row"
+										],
+										"description": "Action type: query (default) or update-row"
+									},
+									"columnValues": {
+										"type": "string",
+										"description": "Changed column-value pairs as JSON string for update-row action"
+									},
+									"datasource": {
+										"type": "string",
+										"description": "Name of the DataSource bean in the registry (auto-detected if only one exists)"
+									},
+									"maxRows": {
+										"type": "integer",
+										"default": 100,
+										"description": "Maximum number of rows to return"
+									},
+									"primaryKeyValues": {
+										"type": "string",
+										"description": "Primary key column-value pairs as JSON string for update-row action"
+									},
+									"queryTimeout": {
+										"type": "integer",
+										"default": 30,
+										"description": "Query timeout in seconds"
+									},
+									"sql": {
+										"type": "string",
+										"description": "The SQL query to execute"
+									},
+									"table": {
+										"type": "string",
+										"description": "Table name for update-row action"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "SQL Query output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/sql-trace": {
+			"get": {
+				"summary": "SQL Trace",
+				"description": "Trace SQL query executions",
+				"operationId": "sql-trace",
+				"responses": {
+					"200": {
+						"description": "SQL Trace output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/startup-recorder": {
+			"get": {
+				"summary": "Startup Recorder",
+				"description": "Starting recording information",
+				"operationId": "startup-recorder",
+				"responses": {
+					"200": {
+						"description": "Startup Recorder output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/stub": {
+			"get": {
+				"summary": "Stub",
+				"description": "Browse messages on stub endpoints",
+				"operationId": "stub",
+				"parameters": [
+					{
+						"name": "browse",
+						"in": "query",
+						"description": "Whether to browse messages",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					},
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the routes matching by queue name",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "format",
+						"in": "query",
+						"description": "To use either xml or json output format",
+						"required": false,
+						"schema": {
+							"type": "string",
+							"enum": [
+								"xml",
+								"json"
+							]
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of messages dumped",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Stub output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/system-properties": {
+			"get": {
+				"summary": "System Properties",
+				"description": "Displays Java System Properties",
+				"operationId": "system-properties",
+				"responses": {
+					"200": {
+						"description": "System Properties output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/thread": {
+			"get": {
+				"summary": "Thread",
+				"description": "Displays JVM Threads information",
+				"operationId": "thread",
+				"parameters": [
+					{
+						"name": "stackTrace",
+						"in": "query",
+						"description": "Whether to include thread stack traces",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": false
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Thread output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/top": {
+			"get": {
+				"summary": "Top Routes",
+				"description": "Display the top routes",
+				"operationId": "top",
+				"parameters": [
+					{
+						"name": "filter",
+						"in": "query",
+						"description": "Filters the routes and processors matching by route id, route uri, processor id, and source location",
+						"required": false,
+						"schema": {
+							"type": "string"
+						}
+					},
+					{
+						"name": "limit",
+						"in": "query",
+						"description": "Limits the number of entries displayed",
+						"required": false,
+						"schema": {
+							"type": "integer"
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Top Routes output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/trace": {
+			"post": {
+				"summary": "Camel Tracing",
+				"description": "Trace routed messages",
+				"operationId": "trace",
+				"requestBody": {
+					"content": {
+						"application\/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"dump": {
+										"type": "string",
+										"enum": [
+											"true",
+											"false"
+										],
+										"description": "Whether to dump trace messages"
+									},
+									"enabled": {
+										"type": "string",
+										"enum": [
+											"true",
+											"false"
+										],
+										"description": "Whether to enable or disable tracing"
+									}
+								}
+							}
+						}
+					}
+				},
+				"responses": {
+					"200": {
+						"description": "Camel Tracing output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/transformers": {
+			"get": {
+				"summary": "Data Type Transformers",
+				"description": "Data-type transformer information",
+				"operationId": "transformers",
+				"responses": {
+					"200": {
+						"description": "Data Type Transformers output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/type-converters": {
+			"get": {
+				"summary": "Type Converters",
+				"description": "Camel Type Converter information",
+				"operationId": "type-converters",
+				"responses": {
+					"200": {
+						"description": "Type Converters output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/variables": {
+			"get": {
+				"summary": "Variables",
+				"description": "Displays variables",
+				"operationId": "variables",
+				"responses": {
+					"200": {
+						"description": "Variables output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		},
+		"\/q\/dev\/vertx-websocket": {
+			"get": {
+				"summary": "Vert.x WebSocket",
+				"description": "Vert.x WebSocket consumer details",
+				"operationId": "vertx-websocket",
+				"parameters": [
+					{
+						"name": "includeHeaders",
+						"in": "query",
+						"description": "Whether to include WebSocket peer connection header details in the output",
+						"required": false,
+						"schema": {
+							"type": "boolean",
+							"default": true
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "Vert.x WebSocket output",
+						"content": {
+							"application\/json": {
+								
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
diff --git a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java
index e6da89abff48..d71a426840bc 100644
--- a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java
+++ b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java
@@ -599,6 +599,11 @@ public interface CamelCatalog {
      */
     String listDevConsolesAsJson();
 
+    /**
+     * The OpenAPI 3.0 specification describing every dev console endpoint, aggregated across all dev consoles.
+     */
+    String devConsolesOpenApiSpec();
+
     /**
      * Lists all the models (EIPs) summary details in JSon
      */
diff --git a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java
index 8bb8ba3d8d83..67d0a9c19138 100644
--- a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java
+++ b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java
@@ -546,6 +546,11 @@ public class DefaultCamelCatalog extends AbstractCachingCamelCatalog implements
                 .toList()));
     }
 
+    @Override
+    public String devConsolesOpenApiSpec() {
+        return cache(BASE_RESOURCE_DIR + "/dev-consoles-openapi.json", this::loadResource);
+    }
+
     @Override
     public String listModelsAsJson() {
         return cache(LIST_MODELS_AS_JSON, () -> JsonMapper.serialize(findModelNames().stream()
diff --git a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
index f5217b7dbe86..dda65723921c 100644
--- a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
+++ b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
@@ -38,6 +38,7 @@ import org.apache.camel.tooling.model.LanguageModel;
 import org.apache.camel.tooling.model.PojoBeanModel;
 import org.apache.camel.tooling.model.ReleaseModel;
 import org.apache.camel.tooling.model.SecurityAdvisoryModel;
+import org.apache.camel.util.json.JsonObject;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -1772,6 +1773,30 @@ public class CamelCatalogTest {
         Assertions.assertTrue(advisory.getComponents().contains("camel-bean"));
     }
 
+    @Test
+    public void devConsolesOpenApiSpec() {
+        String json = catalog.devConsolesOpenApiSpec();
+        Assertions.assertNotNull(json);
+
+        JsonObject doc = JsonMapper.deserialize(json);
+        Assertions.assertEquals("3.0.3", doc.getString("openapi"));
+
+        JsonObject paths = doc.getJsonObject("paths");
+        Assertions.assertNotNull(paths);
+
+        JsonObject context = paths.getJsonObject("/q/dev/context");
+        Assertions.assertNotNull(context);
+        Assertions.assertNotNull(context.getJsonObject("get"));
+        Assertions.assertNull(context.get("post"));
+
+        JsonObject route = paths.getJsonObject("/q/dev/route");
+        Assertions.assertNotNull(route);
+        Assertions.assertNull(route.get("get"));
+        JsonObject post = route.getJsonObject("post");
+        Assertions.assertNotNull(post);
+        Assertions.assertNotNull(post.getJsonObject("requestBody"));
+    }
+
     @Test
     public void testFindPojoBeanNames() {
         List<String> names = catalog.findBeansNames();
diff --git a/core/camel-console/pom.xml b/core/camel-console/pom.xml
index c09ab7f98d73..e6f22770d58a 100644
--- a/core/camel-console/pom.xml
+++ b/core/camel-console/pom.xml
@@ -49,6 +49,11 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-util-json</artifactId>
         </dependency>
+        <!-- tooling model (for building the api console's OpenAPI spec) -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-tooling-model</artifactId>
+        </dependency>
 
         <!-- testing -->
         <dependency>
diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java
index 2fe27561dda6..2b1cc6b1c101 100644
--- a/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java
+++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.impl.console;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -24,7 +25,9 @@ import org.apache.camel.console.DevConsoleRegistry;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.annotations.DevConsole;
 import org.apache.camel.support.console.AbstractDevConsole;
-import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.tooling.model.DevConsoleModel;
+import org.apache.camel.tooling.model.DevConsoleOpenApiHelper;
+import org.apache.camel.tooling.model.JsonMapper;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
 
@@ -63,183 +66,37 @@ public class ApiDevConsole extends AbstractDevConsole {
     }
 
     private String buildOpenApi() {
-        JsonObject root = new JsonObject();
-        root.put("openapi", "3.0.3");
-
-        JsonObject info = new JsonObject();
-        info.put("title", "Camel Dev Console API");
-        info.put("version", getCamelContext().getVersion());
-        root.put("info", info);
-
-        JsonObject paths = new JsonObject();
+        List<DevConsoleModel> models = new ArrayList<>();
 
         DevConsoleRegistry dcr = getCamelContext().getCamelContextExtension()
                 .getContextPlugin(DevConsoleRegistry.class);
         if (dcr != null && dcr.isEnabled()) {
-            List<org.apache.camel.console.DevConsole> consoles = dcr.stream()
-                    .sorted((a, b) -> a.getId().compareToIgnoreCase(b.getId()))
-                    .toList();
-
-            for (org.apache.camel.console.DevConsole console : consoles) {
-                String id = console.getId();
-                boolean readOnly = console.isReadOnly();
-
-                JsonObject pathItem = new JsonObject();
-                JsonObject operation = new JsonObject();
-                operation.put("summary", console.getDisplayName());
-                operation.put("description", console.getDescription());
-                operation.put("operationId", id);
-
-                if (readOnly) {
-                    JsonArray parameters = buildConsoleParameters(id);
-                    if (parameters != null) {
-                        operation.put("parameters", parameters);
-                    }
-                } else {
-                    JsonObject requestBody = buildConsoleRequestBody(id);
-                    if (requestBody != null) {
-                        operation.put("requestBody", requestBody);
-                    }
-                }
-
-                JsonObject responses = new JsonObject();
-                JsonObject ok = new JsonObject();
-                ok.put("description", console.getDisplayName() + " output");
-                JsonObject responseContent = new JsonObject();
-                responseContent.put("application/json", new JsonObject());
-                ok.put("content", responseContent);
-                responses.put("200", ok);
-                operation.put("responses", responses);
-
-                pathItem.put(readOnly ? "get" : "post", operation);
-                paths.put("/q/dev/" + id, pathItem);
+            for (org.apache.camel.console.DevConsole console : dcr.stream().toList()) {
+                models.add(buildConsoleModel(console));
             }
         }
 
-        root.put("paths", paths);
+        JsonObject root = DevConsoleOpenApiHelper.buildOpenApiDocument(models, getCamelContext().getVersion());
         return Jsoner.prettyPrint(root.toJson());
     }
 
     /**
-     * Loads and parses the option schema for the given console.
-     *
-     * @return the parsed options, or null if the console has no options or the schema could not be loaded
+     * Builds the {@link DevConsoleModel} for the given live console: its options are loaded from the console's
+     * generated catalog schema, while id/displayName/description/readOnly come from the live instance.
      */
-    private JsonObject loadConsoleOptions(String consoleId) {
+    private DevConsoleModel buildConsoleModel(org.apache.camel.console.DevConsole console) {
+        DevConsoleModel model;
         try {
             String json = ((CatalogCamelContext) getCamelContext())
-                    .getDevConsoleParameterJsonSchema(consoleId);
-            if (json == null) {
-                return null;
-            }
-            Object parsed = Jsoner.deserialize(json);
-            if (!(parsed instanceof JsonObject jo)) {
-                return null;
-            }
-            Object optionsObj = jo.get("options");
-            if (!(optionsObj instanceof JsonObject opts) || opts.isEmpty()) {
-                return null;
-            }
-            return opts;
+                    .getDevConsoleParameterJsonSchema(console.getId());
+            model = json != null ? JsonMapper.generateDevConsoleModel(json) : new DevConsoleModel();
         } catch (Exception e) {
-            // ignore
-            return null;
-        }
-    }
-
-    private JsonObject buildConsoleRequestBody(String consoleId) {
-        JsonObject opts = loadConsoleOptions(consoleId);
-        if (opts == null) {
-            return null;
-        }
-
-        JsonObject properties = new JsonObject();
-        JsonArray required = new JsonArray();
-        for (Map.Entry<String, Object> entry : opts.entrySet()) {
-            String name = entry.getKey();
-            if (!(entry.getValue() instanceof JsonObject opt)) {
-                continue;
-            }
-            JsonObject prop = new JsonObject();
-            String type = opt.getString("type");
-            if (type != null) {
-                prop.put("type", type);
-            }
-            String description = opt.getString("description");
-            if (description != null) {
-                prop.put("description", description);
-            }
-            Object defaultValue = opt.get("defaultValue");
-            if (defaultValue != null) {
-                prop.put("default", defaultValue);
-            }
-            Object enumValues = opt.get("enum");
-            if (enumValues instanceof JsonArray ea && !ea.isEmpty()) {
-                prop.put("enum", enumValues);
-            }
-            properties.put(name, prop);
-
-            Boolean req = opt.getBoolean("required");
-            if (req != null && req) {
-                required.add(name);
-            }
-        }
-
-        JsonObject schema = new JsonObject();
-        schema.put("type", "object");
-        schema.put("properties", properties);
-        if (!required.isEmpty()) {
-            schema.put("required", required);
-        }
-
-        JsonObject mediaType = new JsonObject();
-        mediaType.put("schema", schema);
-        JsonObject content = new JsonObject();
-        content.put("application/json", mediaType);
-        JsonObject requestBody = new JsonObject();
-        requestBody.put("content", content);
-        return requestBody;
-    }
-
-    private JsonArray buildConsoleParameters(String consoleId) {
-        JsonObject opts = loadConsoleOptions(consoleId);
-        if (opts == null) {
-            return null;
-        }
-
-        JsonArray parameters = new JsonArray();
-        for (Map.Entry<String, Object> entry : opts.entrySet()) {
-            String name = entry.getKey();
-            if (!(entry.getValue() instanceof JsonObject opt)) {
-                continue;
-            }
-            JsonObject param = new JsonObject();
-            param.put("name", name);
-            param.put("in", "query");
-            String description = opt.getString("description");
-            if (description != null) {
-                param.put("description", description);
-            }
-            Boolean req = opt.getBoolean("required");
-            param.put("required", req != null && req);
-
-            JsonObject schema = new JsonObject();
-            String type = opt.getString("type");
-            if (type != null) {
-                schema.put("type", type);
-            }
-            Object defaultValue = opt.get("defaultValue");
-            if (defaultValue != null) {
-                schema.put("default", defaultValue);
-            }
-            Object enumValues = opt.get("enum");
-            if (enumValues instanceof JsonArray ea && !ea.isEmpty()) {
-                schema.put("enum", enumValues);
-            }
-            param.put("schema", schema);
-
-            parameters.add(param);
+            model = new DevConsoleModel();
         }
-        return parameters.isEmpty() ? null : parameters;
+        model.setName(console.getId());
+        model.setTitle(console.getDisplayName());
+        model.setDescription(console.getDescription());
+        model.setReadOnly(console.isReadOnly());
+        return model;
     }
 }
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java
index 48eea3fabc1a..74a4afa5ee0f 100644
--- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java
@@ -19,6 +19,7 @@ package org.apache.camel.tooling.model;
 public class DevConsoleModel extends ArtifactModel<DevConsoleModel.DevConsoleOptionModel> {
 
     protected String group;
+    protected boolean readOnly = true;
 
     public DevConsoleModel() {
     }
@@ -31,6 +32,14 @@ public class DevConsoleModel extends ArtifactModel<DevConsoleModel.DevConsoleOpt
         this.group = group;
     }
 
+    public boolean isReadOnly() {
+        return readOnly;
+    }
+
+    public void setReadOnly(boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+
     @Override
     public Kind getKind() {
         return Kind.console;
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelper.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelper.java
new file mode 100644
index 000000000000..4bbdbd40e855
--- /dev/null
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelper.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.tooling.model;
+
+import java.util.List;
+
+import org.apache.camel.tooling.model.DevConsoleModel.DevConsoleOptionModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * Builds an OpenAPI 3.0 specification describing the dev console {@code /q/dev/{id}} endpoints, from
+ * {@link DevConsoleModel} instances. Shared between the live {@code api} dev console (which sources models from a
+ * running {@code DevConsoleRegistry}) and the build-time catalog generator (which sources models from the aggregated
+ * catalog {@code dev-console/*.json} files), so both produce identical output.
+ */
+public final class DevConsoleOpenApiHelper {
+
+    private DevConsoleOpenApiHelper() {
+    }
+
+    /**
+     * Builds a full OpenAPI document for the given consoles.
+     */
+    public static JsonObject buildOpenApiDocument(List<DevConsoleModel> consoles, String version) {
+        JsonObject root = new JsonObject();
+        root.put("openapi", "3.0.3");
+
+        JsonObject info = new JsonObject();
+        info.put("title", "Camel Dev Console API");
+        info.put("version", version);
+        root.put("info", info);
+
+        JsonObject paths = new JsonObject();
+        consoles.stream()
+                .sorted((a, b) -> a.getName().compareToIgnoreCase(b.getName()))
+                .forEach(model -> paths.put("/q/dev/" + model.getName(), buildPathItem(model)));
+        root.put("paths", paths);
+
+        return root;
+    }
+
+    /**
+     * Builds the OpenAPI path item ({@code get} or {@code post} operation) for a single console.
+     */
+    public static JsonObject buildPathItem(DevConsoleModel model) {
+        JsonObject operation = new JsonObject();
+        operation.put("summary", model.getTitle());
+        operation.put("description", model.getDescription());
+        operation.put("operationId", model.getName());
+
+        boolean readOnly = model.isReadOnly();
+        if (readOnly) {
+            JsonArray parameters = buildParameters(model.getOptions());
+            if (parameters != null) {
+                operation.put("parameters", parameters);
+            }
+        } else {
+            JsonObject requestBody = buildRequestBody(model.getOptions());
+            if (requestBody != null) {
+                operation.put("requestBody", requestBody);
+            }
+        }
+
+        JsonObject responses = new JsonObject();
+        JsonObject ok = new JsonObject();
+        ok.put("description", model.getTitle() + " output");
+        JsonObject responseContent = new JsonObject();
+        responseContent.put("application/json", new JsonObject());
+        ok.put("content", responseContent);
+        responses.put("200", ok);
+        operation.put("responses", responses);
+
+        JsonObject pathItem = new JsonObject();
+        pathItem.put(readOnly ? "get" : "post", operation);
+        return pathItem;
+    }
+
+    private static JsonArray buildParameters(List<DevConsoleOptionModel> options) {
+        if (options == null || options.isEmpty()) {
+            return null;
+        }
+
+        JsonArray parameters = new JsonArray();
+        for (DevConsoleOptionModel opt : options) {
+            JsonObject param = new JsonObject();
+            param.put("name", opt.getName());
+            param.put("in", "query");
+            if (opt.getDescription() != null) {
+                param.put("description", opt.getDescription());
+            }
+            param.put("required", opt.isRequired());
+            param.put("schema", buildSchema(opt));
+            parameters.add(param);
+        }
+        return parameters;
+    }
+
+    private static JsonObject buildRequestBody(List<DevConsoleOptionModel> options) {
+        if (options == null || options.isEmpty()) {
+            return null;
+        }
+
+        JsonObject properties = new JsonObject();
+        JsonArray required = new JsonArray();
+        for (DevConsoleOptionModel opt : options) {
+            JsonObject prop = buildSchema(opt);
+            if (opt.getDescription() != null) {
+                prop.put("description", opt.getDescription());
+            }
+            properties.put(opt.getName(), prop);
+            if (opt.isRequired()) {
+                required.add(opt.getName());
+            }
+        }
+
+        JsonObject schema = new JsonObject();
+        schema.put("type", "object");
+        schema.put("properties", properties);
+        if (!required.isEmpty()) {
+            schema.put("required", required);
+        }
+
+        JsonObject mediaType = new JsonObject();
+        mediaType.put("schema", schema);
+        JsonObject content = new JsonObject();
+        content.put("application/json", mediaType);
+        JsonObject requestBody = new JsonObject();
+        requestBody.put("content", content);
+        return requestBody;
+    }
+
+    private static JsonObject buildSchema(DevConsoleOptionModel opt) {
+        JsonObject schema = new JsonObject();
+        if (opt.getType() != null) {
+            schema.put("type", opt.getType());
+        }
+        if (opt.getDefaultValue() != null) {
+            schema.put("default", opt.resolveDefaultValue());
+        }
+        if (opt.getEnums() != null && !opt.getEnums().isEmpty()) {
+            schema.put("enum", new JsonArray(opt.getEnums()));
+        }
+        return schema;
+    }
+}
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
index 40723b4e4acb..46903a93bd7b 100644
--- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
@@ -494,6 +494,7 @@ public final class JsonMapper {
         DevConsoleModel model = new DevConsoleModel();
         parseModel(mobj, model);
         model.setGroup(mobj.getString("group"));
+        model.setReadOnly(mobj.getBooleanOrDefault("readOnly", true));
         parseArtifact(mobj, model);
         JsonObject options = (JsonObject) obj.get("options");
         if (options != null) {
@@ -517,6 +518,7 @@ public final class JsonMapper {
         baseToJson(model, obj);
         artifactToJson(model, obj);
         obj.put("group", model.getGroup());
+        obj.put("readOnly", model.isReadOnly());
         obj.entrySet().removeIf(e -> e.getValue() == null);
         JsonObject wrapper = new JsonObject();
         wrapper.put("console", obj);
diff --git a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelperTest.java b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelperTest.java
new file mode 100644
index 000000000000..f92b673b1227
--- /dev/null
+++ b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelperTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.tooling.model;
+
+import java.util.List;
+
+import org.apache.camel.tooling.model.DevConsoleModel.DevConsoleOptionModel;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The unit test class for {@link DevConsoleOpenApiHelper}.
+ */
+class DevConsoleOpenApiHelperTest {
+
+    private static DevConsoleOptionModel option(String name, String type, boolean required) {
+        DevConsoleOptionModel option = new DevConsoleOptionModel();
+        option.setName(name);
+        option.setType(type);
+        option.setRequired(required);
+        option.setDescription("The " + name);
+        return option;
+    }
+
+    @Test
+    void readOnlyConsoleWithOptionsBuildsGetWithParameters() {
+        DevConsoleModel model = new DevConsoleModel();
+        model.setName("route");
+        model.setTitle("Route");
+        model.setDescription("Route information");
+        model.setReadOnly(true);
+        model.addOption(option("filter", "string", false));
+        model.addOption(option("limit", "integer", false));
+
+        JsonObject pathItem = DevConsoleOpenApiHelper.buildPathItem(model);
+
+        assertNull(pathItem.get("post"));
+        JsonObject get = pathItem.getJsonObject("get");
+        assertNotNull(get);
+        assertEquals("route", get.getString("operationId"));
+        assertNull(get.get("requestBody"));
+        var parameters = get.getCollection("parameters");
+        assertEquals(2, parameters.size());
+    }
+
+    @Test
+    void mutatingConsoleWithOptionsBuildsPostWithRequestBody() {
+        DevConsoleModel model = new DevConsoleModel();
+        model.setName("route");
+        model.setTitle("Route");
+        model.setDescription("Route information");
+        model.setReadOnly(false);
+        model.addOption(option("action", "string", true));
+
+        JsonObject pathItem = DevConsoleOpenApiHelper.buildPathItem(model);
+
+        assertNull(pathItem.get("get"));
+        JsonObject post = pathItem.getJsonObject("post");
+        assertNotNull(post);
+        assertNull(post.get("parameters"));
+        JsonObject requestBody = post.getJsonObject("requestBody");
+        assertNotNull(requestBody);
+        JsonObject schema = requestBody.getJsonObject("content").getJsonObject("application/json").getJsonObject("schema");
+        assertTrue(schema.getCollection("required").contains("action"));
+    }
+
+    @Test
+    void consoleWithoutOptionsHasNeitherParametersNorRequestBody() {
+        DevConsoleModel model = new DevConsoleModel();
+        model.setName("context");
+        model.setTitle("CamelContext");
+        model.setDescription("Overall information about the CamelContext");
+        model.setReadOnly(true);
+
+        JsonObject pathItem = DevConsoleOpenApiHelper.buildPathItem(model);
+
+        JsonObject get = pathItem.getJsonObject("get");
+        assertNotNull(get);
+        assertNull(get.get("parameters"));
+        assertNull(get.get("requestBody"));
+    }
+
+    @Test
+    void openApiDocumentContainsOnePathPerConsoleSortedByName() {
+        DevConsoleModel context = new DevConsoleModel();
+        context.setName("context");
+        context.setTitle("CamelContext");
+        context.setDescription("Overall information about the CamelContext");
+        context.setReadOnly(true);
+
+        DevConsoleModel route = new DevConsoleModel();
+        route.setName("route");
+        route.setTitle("Route");
+        route.setDescription("Route information");
+        route.setReadOnly(false);
+
+        JsonObject doc = DevConsoleOpenApiHelper.buildOpenApiDocument(List.of(route, context), "4.23.0-SNAPSHOT");
+
+        assertEquals("3.0.3", doc.getString("openapi"));
+        assertEquals("4.23.0-SNAPSHOT", doc.getJsonObject("info").getString("version"));
+        JsonObject paths = doc.getJsonObject("paths");
+        assertNotNull(paths.getJsonObject("/q/dev/context").getJsonObject("get"));
+        assertNotNull(paths.getJsonObject("/q/dev/route").getJsonObject("post"));
+        assertFalse(paths.containsKey("/q/dev/missing"));
+    }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java
index 7ce21e3fa9c2..09c32609049b 100644
--- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java
@@ -48,6 +48,7 @@ import org.apache.camel.tooling.model.BaseOptionModel;
 import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.tooling.model.DataFormatModel;
 import org.apache.camel.tooling.model.DevConsoleModel;
+import org.apache.camel.tooling.model.DevConsoleOpenApiHelper;
 import org.apache.camel.tooling.model.EipModel;
 import org.apache.camel.tooling.model.JsonMapper;
 import org.apache.camel.tooling.model.LanguageModel;
@@ -59,6 +60,8 @@ import org.apache.camel.tooling.model.TransformerModel;
 import org.apache.camel.tooling.util.FileUtil;
 import org.apache.camel.tooling.util.PackageHelper;
 import org.apache.camel.tooling.util.Strings;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
@@ -899,6 +902,15 @@ public class PrepareCatalogMojo extends AbstractMojo {
                 = jsonFiles.stream().map(PrepareCatalogMojo::asComponentName).collect(Collectors.toCollection(TreeSet::new));
         FileUtil.updateFile(all, String.join("\n", consoleNames) + "\n");
 
+        // build and write a single consolidated OpenAPI document describing every dev console
+        List<DevConsoleModel> models = jsonFiles.stream()
+                .map(p -> (DevConsoleModel) allModels.get(p))
+                .collect(Collectors.toList());
+        JsonObject openApi = DevConsoleOpenApiHelper.buildOpenApiDocument(models, project.getVersion());
+        Path openApiFile = consolesOutDir.resolve("../dev-consoles-openapi.json");
+        FileUtil.updateFile(openApiFile, Jsoner.prettyPrint(openApi.toJson()) + "\n");
+        getLog().info("Generated dev-consoles-openapi.json containing " + models.size() + " Camel dev console paths");
+
         printConsolesReport(jsonFiles, duplicateJsonFiles);
 
         return consoleNames;