Index: trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java	(revision 16824)
+++ trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java	(revision 16825)
@@ -32,5 +32,5 @@
      */
     static final int protocolMajorVersion = 1;
-    static final int protocolMinorVersion = 10;
+    static final int protocolMinorVersion = 12;
 
     /**
@@ -99,3 +99,11 @@
         throw new UnknownHostException();
     }
+
+    /**
+     * Returns the RemoteControl HTTP protocol version
+     * @return the RemoteControl HTTP protocol version
+     */
+    public static String getVersion() {
+        return protocolMajorVersion + "." + protocolMinorVersion;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java	(revision 16824)
+++ trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java	(revision 16825)
@@ -11,5 +11,4 @@
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
@@ -24,11 +23,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonArrayBuilder;
-import javax.json.JsonObject;
-import javax.json.JsonObjectBuilder;
 
 import org.openstreetmap.josm.data.Version;
@@ -42,4 +37,5 @@
 import org.openstreetmap.josm.io.remotecontrol.handler.LoadDataHandler;
 import org.openstreetmap.josm.io.remotecontrol.handler.LoadObjectHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.OpenApiHandler;
 import org.openstreetmap.josm.io.remotecontrol.handler.OpenFileHandler;
 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
@@ -62,4 +58,9 @@
 
     /**
+     * The string "JOSM RemoteControl"
+     */
+    public static final String JOSM_REMOTE_CONTROL = "JOSM RemoteControl";
+
+    /**
      * RemoteControl protocol version. Change minor number for compatible
      * interface extensions. Change major number in case of incompatible
@@ -70,5 +71,5 @@
                     .add("major", RemoteControl.protocolMajorVersion)
                     .add("minor", RemoteControl.protocolMinorVersion))
-            .add("application", "JOSM RemoteControl")
+            .add("application", JOSM_REMOTE_CONTROL)
             .add("version", Version.getInstance().getVersion())
             .build().toString();
@@ -168,4 +169,5 @@
             addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true);
             addRequestHandlerClass(FeaturesHandler.command, FeaturesHandler.class, true);
+            addRequestHandlerClass(OpenApiHandler.command, OpenApiHandler.class, true);
         }
     }
@@ -393,5 +395,5 @@
         out.write("HTTP/1.1 " + status + "\r\n");
         out.write("Date: " + new Date() + "\r\n");
-        out.write("Server: JOSM RemoteControl\r\n");
+        out.write("Server: " + JOSM_REMOTE_CONTROL + "\r\n");
         out.write("Content-type: " + contentType + "; charset=" + RESPONSE_CHARSET.name().toLowerCase(Locale.ENGLISH) + "\r\n");
         out.write("Access-Control-Allow-Origin: *\r\n");
@@ -401,26 +403,20 @@
 
     /**
-     * Returns the JSON information for the given (if null: all) handlers.
+     * Returns the information for the given (if null: all) handlers.
      * @param handlers the handlers
-     * @return the JSON information for the given (if null: all) handlers
-     */
-    public static JsonArray getHandlersInfoAsJSON(Collection<String> handlers) {
-        JsonArrayBuilder json = Json.createArrayBuilder();
-        for (String s : Utils.firstNonNull(handlers, RequestProcessor.handlers.keySet())) {
-            JsonObject infoAsJson = getHandlerInfoAsJSON(s);
-            if (infoAsJson != null) {
-                json.add(infoAsJson);
-            }
-        }
-        return json.build();
-    }
-
-    /**
-     * Returns the JSON information for a given handler.
+     * @return the information for the given (if null: all) handlers
+     */
+    public static Stream<RequestHandler> getHandlersInfo(Collection<String> handlers) {
+        return Utils.firstNonNull(handlers, RequestProcessor.handlers.keySet()).stream()
+                .map(RequestProcessor::getHandlerInfo)
+                .filter(Objects::nonNull);
+    }
+
+    /**
+     * Returns the information for a given handler.
      * @param cmd handler key
-     * @return JSON information for the given handler
-     */
-    public static JsonObject getHandlerInfoAsJSON(String cmd) {
-        RequestHandler handler;
+     * @return the information for the given handler
+     */
+    public static RequestHandler getHandlerInfo(String cmd) {
         if (cmd == null) {
             return null;
@@ -432,5 +428,7 @@
             Class<?> c = handlers.get(cmd);
             if (c == null) return null;
-            handler = handlers.get(cmd).getConstructor().newInstance();
+            RequestHandler handler = handlers.get(cmd).getConstructor().newInstance();
+            handler.setCommand(cmd);
+            return handler;
         } catch (ReflectiveOperationException ex) {
             Logging.warn("Unknown handler " + cmd);
@@ -438,23 +436,4 @@
             return null;
         }
-        return getHandlerInfoAsJSON(cmd, handler);
-    }
-
-    private static JsonObject getHandlerInfoAsJSON(String cmd, RequestHandler handler) {
-        JsonObjectBuilder json = Json.createObjectBuilder();
-        json.add("request", cmd);
-        if (handler.getUsage() != null) {
-            json.add("usage", handler.getUsage());
-        }
-        json.add("parameters", toJsonArray(handler.getMandatoryParams()));
-        json.add("optional", toJsonArray(handler.getOptionalParams()));
-        json.add("examples", toJsonArray(handler.getUsageExamples(cmd.substring(1))));
-        return json.build();
-    }
-
-    private static JsonArray toJsonArray(String[] strings) {
-        return Arrays.stream(strings)
-                .collect(Collectors.collectingAndThen(Collectors.toList(), Json::createArrayBuilder))
-                .build();
     }
 
Index: trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/FeaturesHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/FeaturesHandler.java	(revision 16824)
+++ trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/FeaturesHandler.java	(revision 16825)
@@ -6,4 +6,11 @@
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.stream.Collectors;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
 
 import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
@@ -25,9 +32,35 @@
         String q = args.get("q");
         Collection<String> handlers = q == null ? null : Arrays.asList(q.split("[,\\s]+", -1));
-        content = RequestProcessor.getHandlersInfoAsJSON(handlers).toString();
+        content = getHandlersInfoAsJSON(handlers).toString();
         contentType = "application/json";
         if (args.containsKey("jsonp")) {
             content = args.get("jsonp") + " && " + args.get("jsonp") + '(' + content + ')';
         }
+    }
+
+    private static JsonArray getHandlersInfoAsJSON(Collection<String> handlers) {
+        JsonArrayBuilder json = Json.createArrayBuilder();
+        RequestProcessor.getHandlersInfo(handlers)
+                .map(FeaturesHandler::getHandlerInfoAsJSON)
+                .forEach(json::add);
+        return json.build();
+    }
+
+    private static JsonObject getHandlerInfoAsJSON(RequestHandler handler) {
+        JsonObjectBuilder json = Json.createObjectBuilder();
+        json.add("request", handler.getCommand());
+        if (handler.getUsage() != null) {
+            json.add("usage", handler.getUsage());
+        }
+        json.add("parameters", toJsonArray(handler.getMandatoryParams()));
+        json.add("optional", toJsonArray(handler.getOptionalParams()));
+        json.add("examples", toJsonArray(handler.getUsageExamples(handler.getCommand())));
+        return json.build();
+    }
+
+    private static JsonArray toJsonArray(String[] strings) {
+        return Arrays.stream(strings)
+                .collect(Collectors.collectingAndThen(Collectors.toList(), Json::createArrayBuilder))
+                .build();
     }
 
Index: trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/OpenApiHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/OpenApiHandler.java	(revision 16825)
+++ trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/OpenApiHandler.java	(revision 16825)
@@ -0,0 +1,117 @@
+// License: GPL. For details, see LICENSE file.
+
+package org.openstreetmap.josm.io.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+
+import org.openstreetmap.josm.data.preferences.JosmUrls;
+import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
+import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
+import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Reports available commands as <a href="http://spec.openapis.org/oas/v3.0.3">OpenAPI</a>.
+ *
+ * @apiNote https://www.openapis.org/
+ */
+public class OpenApiHandler extends RequestHandler {
+
+    /**
+     * The remote control command name.
+     */
+    public static final String command = "openapi.json";
+
+    @Override
+    protected void handleRequest() {
+        JsonObjectBuilder openapi = getOpenApi();
+        StringWriter stringWriter = new StringWriter();
+        Json.createWriter(stringWriter).write(openapi.build());
+        content = stringWriter.toString();
+        contentType = "application/json";
+    }
+
+    private JsonObjectBuilder getOpenApi() {
+        return Json.createObjectBuilder()
+                .add("openapi", "3.0.0")
+                .add("info", Json.createObjectBuilder()
+                        .add("title", RequestProcessor.JOSM_REMOTE_CONTROL)
+                        .add("version", RemoteControl.getVersion())
+                        .add("contact", Json.createObjectBuilder()
+                                .add("name", "JOSM")
+                                .add("url", JosmUrls.getInstance().getJOSMWebsite())))
+                .add("servers", Json.createArrayBuilder()
+                        .add(Json.createObjectBuilder().add("url", "http://localhost:8111/")))
+                .add("paths", getHandlers());
+    }
+
+    private JsonObjectBuilder getHandlers() {
+        JsonObjectBuilder paths = Json.createObjectBuilder();
+        RequestProcessor.getHandlersInfo(null)
+                .forEach(handler -> paths.add("/" + handler.getCommand(), getHandler(handler)));
+        return paths;
+    }
+
+    private JsonObjectBuilder getHandler(RequestHandler handler) {
+        JsonArrayBuilder parameters = Json.createArrayBuilder();
+        Stream.concat(
+                Arrays.stream(handler.getMandatoryParams()),
+                Arrays.stream(handler.getOptionalParams())
+        ).distinct().map(param -> Json.createObjectBuilder()
+                .add("name", param)
+                .add("in", "query")
+                .add("required", Arrays.asList(handler.getMandatoryParams()).contains(param))
+                .add("schema", Json.createObjectBuilder().add("type", "string")) // TODO fix type
+        ).forEach(parameters::add);
+        return Json.createObjectBuilder().add("get", Json.createObjectBuilder()
+                .add("description", getDescription(handler))
+                .add("operationId", handler.getCommand())
+                .add("parameters", parameters)
+                .add("responses", Json.createObjectBuilder()
+                        .add("200", Json.createObjectBuilder().add("description", "successful operation")))
+        );
+    }
+
+    private String getDescription(RequestHandler handler) {
+        return Utils.firstNonNull(handler.getUsage(), "")
+                + "\n\n* " + String.join("\n* ", handler.getUsageExamples());
+    }
+
+    @Override
+    public String getUsage() {
+        return "JOSM RemoteControl as OpenAPI Specification";
+    }
+
+    @Override
+    public String[] getUsageExamples() {
+        return new String[]{"https://petstore.swagger.io/?url=http://localhost:8111/openapi.json", "https://swagger.io/specification/"};
+    }
+
+    @Override
+    public String getPermissionMessage() {
+        return tr("Remote Control has been asked to report its supported features. This enables web sites to guess a running JOSM version");
+    }
+
+    @Override
+    public PermissionPrefWithDefault getPermissionPref() {
+        return PermissionPrefWithDefault.READ_PROTOCOL_VERSION;
+    }
+
+    @Override
+    public String[] getMandatoryParams() {
+        return new String[0];
+    }
+
+    @Override
+    protected void validateRequest() {
+        // Nothing to do
+    }
+}
Index: trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java	(revision 16824)
+++ trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java	(revision 16825)
@@ -310,4 +310,12 @@
         }
         myCommand = command;
+    }
+
+    /**
+     * Returns the command associated with this handler.
+     * @return the command associated with this handler.
+     */
+    public String getCommand() {
+        return myCommand;
     }
 
