{
  "openapi": "3.1.1",
  "info": {
    "title": "RavingDesk GPS File Converter (GPX ↔ KML ↔ GeoJSON) API",
    "version": "1.0.0",
    "summary": "Convert GPX, KML and GeoJSON, simplify tracks and get length, climb, descent and duration.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://ravingdeskgpsfileconverter.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Convert GPX, KML and GeoJSON, simplify tracks and get length, climb, descent and duration.",
        "description": "Converts GPS files between GPX 1.1, KML 2.2 and GeoJSON (waypoints, routes, tracks, placemarks, polygons, names, descriptions, elevations and times where the target format has them), optionally simplifies lines with Douglas–Peucker or Visvalingam–Whyatt at a tolerance in metres, and reports for every track the geodesic length on WGS84, elevation range, climb and descent with a noise threshold, duration and average speed. XML is read with a safe parser that refuses entity declarations. It is the GPS engine of RavingDesk’s Data & charts.\n\nMethodology:\n- GPX 1.1: waypoints become Points, routes and track segments become LineStrings (several segments: a MultiLineString), with name, description, elevation (third coordinate) and time stamps (properties.times). KML 2.2: Placemarks with Point, LineString, LinearRing, Polygon (with holes), MultiGeometry and gx:Track, plus name, description and ExtendedData. Styles, folders, network links, overlays and 3D models are not converted.\n- Writing GPX: points → waypoints, lines → tracks (or routes when the feature came from a GPX route), polygons → closed tracks of their outer rings (GPX has no polygons). Writing KML: one Placemark per feature with name, description and simple properties as ExtendedData; time stamps are not written.\n- XML is parsed by a small reader that refuses DOCTYPE definitions and entity declarations (no XXE, no entity expansion), accepts only the five predefined entities and character references, and limits size, element count and depth.\n- Track length is the geodesic length on WGS84 (Karney’s algorithm); the 3D length adds the elevation change of every segment. Climb and descent use hysteresis: a change counts only after it exceeds the threshold from the last turning point. Duration is last minus first time stamp; the average speed includes stops.\n- Douglas–Peucker keeps every vertex farther than the tolerance from the simplified line; Visvalingam–Whyatt removes the vertex with the smallest triangle area until all are above the tolerance. Both measure in a local equirectangular projection (metres) around each line.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "ravingdesk_gps_file_converter",
        "x-owner-app": "ravingdesk",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RavingdeskGpsFileConverterInput"
              },
              "examples": {
                "example1": {
                  "summary": "GPX track with times and elevations to GeoJSON, with statistics",
                  "value": {
                    "file": {
                      "filename": "tiergarten-loop.gpx",
                      "contentType": "application/gpx+xml",
                      "contentBase64": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGdweCB2ZXJzaW9uPSIxLjEiIGNyZWF0b3I9IlJhdmluZ0Rlc2sgZXhhbXBsZSIgeG1sbnM9Imh0dHA6Ly93d3cudG9wb2dyYWZpeC5jb20vR1BYLzEvMSI+CjxtZXRhZGF0YT48bmFtZT5UaWVyZ2FydGVuIGxvb3AgKGV4YW1wbGUgdHJhY2spPC9uYW1lPjwvbWV0YWRhdGE+Cjx3cHQgbGF0PSI1Mi41MTQ1NDMiIGxvbj0iMTMuMzUwMTA4Ij48ZWxlPjM0PC9lbGU+PG5hbWU+VmljdG9yeSBDb2x1bW48L25hbWU+PC93cHQ+Cjx0cms+PG5hbWU+VGllcmdhcnRlbiBsb29wPC9uYW1lPjx0cmtzZWc+Cjx0cmtwdCBsYXQ9IjUyLjUxNDU0MyIgbG9uPSIxMy4zNTAxMDgiPjxlbGU+MzQ8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjAwOjAwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTYxMDAiIGxvbj0iMTMuMzU1ODAwIj48ZWxlPjM1LjU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjA0OjEwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTc2MDAiIGxvbj0iMTMuMzYyMTAwIj48ZWxlPjM3PC9lbGU+PHRpbWU+MjAyNi0wNS0wMVQwODowODo0MFo8L3RpbWU+PC90cmtwdD4KPHRya3B0IGxhdD0iNTIuNTE2OTAwIiBsb249IjEzLjM2OTUwMCI+PGVsZT4zNjwvZWxlPjx0aW1lPjIwMjYtMDUtMDFUMDg6MTM6MjBaPC90aW1lPjwvdHJrcHQ+Cjx0cmtwdCBsYXQ9IjUyLjUxNjMwMCIgbG9uPSIxMy4zNzc3MDAiPjxlbGU+MzU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjE4OjMwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTM2MDAiIGxvbj0iMTMuMzc2MjAwIj48ZWxlPjM2LjU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjIxOjAwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTEyMDAiIGxvbj0iMTMuMzY2NDAwIj48ZWxlPjM4PC9lbGU+PHRpbWU+MjAyNi0wNS0wMVQwODoyNzowMFo8L3RpbWU+PC90cmtwdD4KPHRya3B0IGxhdD0iNTIuNTEwODAwIiBsb249IjEzLjM1NzkwMCI+PGVsZT4zNjwvZWxlPjx0aW1lPjIwMjYtMDUtMDFUMDg6MzI6MjBaPC90aW1lPjwvdHJrcHQ+Cjx0cmtwdCBsYXQ9IjUyLjUxMjYwMCIgbG9uPSIxMy4zNTE4MDAiPjxlbGU+MzU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjM2OjMwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTQ1NDMiIGxvbj0iMTMuMzUwMTA4Ij48ZWxlPjM0PC9lbGU+PHRpbWU+MjAyNi0wNS0wMVQwODozOTowMFo8L3RpbWU+PC90cmtwdD4KPC90cmtzZWc+PC90cms+CjwvZ3B4Pgo="
                    },
                    "to": "geojson"
                  }
                },
                "example2": {
                  "summary": "KML placemarks (point, line, polygon) to GPX",
                  "value": {
                    "file": {
                      "filename": "berlin.kml",
                      "contentType": "application/vnd.google-earth.kml+xml",
                      "contentBase64": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGttbCB4bWxucz0iaHR0cDovL3d3dy5vcGVuZ2lzLm5ldC9rbWwvMi4yIj48RG9jdW1lbnQ+PG5hbWU+QmVybGluIGV4YW1wbGVzPC9uYW1lPgo8UGxhY2VtYXJrPjxuYW1lPkJyYW5kZW5idXJnIEdhdGU8L25hbWU+PFBvaW50Pjxjb29yZGluYXRlcz4xMy4zNzc3MDQsNTIuNTE2Mjc1LDA8L2Nvb3JkaW5hdGVzPjwvUG9pbnQ+PC9QbGFjZW1hcms+CjxQbGFjZW1hcms+PG5hbWU+VW50ZXIgZGVuIExpbmRlbjwvbmFtZT48TGluZVN0cmluZz48Y29vcmRpbmF0ZXM+MTMuMzc3Nyw1Mi41MTYzIDEzLjM4ODksNTIuNTE3MiAxMy4zOTcwLDUyLjUxNzY8L2Nvb3JkaW5hdGVzPjwvTGluZVN0cmluZz48L1BsYWNlbWFyaz4KPFBsYWNlbWFyaz48bmFtZT5QYXJpc2VyIFBsYXR6PC9uYW1lPjxQb2x5Z29uPjxvdXRlckJvdW5kYXJ5SXM+PExpbmVhclJpbmc+PGNvb3JkaW5hdGVzPjEzLjM3ODAsNTIuNTE1OCAxMy4zODA5LDUyLjUxNjEgMTMuMzgwNiw1Mi41MTcyIDEzLjM3NzcsNTIuNTE2OSAxMy4zNzgwLDUyLjUxNTg8L2Nvb3JkaW5hdGVzPjwvTGluZWFyUmluZz48L291dGVyQm91bmRhcnlJcz48L1BvbHlnb24+PC9QbGFjZW1hcms+CjwvRG9jdW1lbnQ+PC9rbWw+Cg=="
                    },
                    "to": "gpx"
                  }
                },
                "example3": {
                  "summary": "GPX track simplified with Douglas–Peucker (20 m) to KML",
                  "value": {
                    "file": {
                      "filename": "tiergarten-loop.gpx",
                      "contentType": "application/gpx+xml",
                      "contentBase64": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGdweCB2ZXJzaW9uPSIxLjEiIGNyZWF0b3I9IlJhdmluZ0Rlc2sgZXhhbXBsZSIgeG1sbnM9Imh0dHA6Ly93d3cudG9wb2dyYWZpeC5jb20vR1BYLzEvMSI+CjxtZXRhZGF0YT48bmFtZT5UaWVyZ2FydGVuIGxvb3AgKGV4YW1wbGUgdHJhY2spPC9uYW1lPjwvbWV0YWRhdGE+Cjx3cHQgbGF0PSI1Mi41MTQ1NDMiIGxvbj0iMTMuMzUwMTA4Ij48ZWxlPjM0PC9lbGU+PG5hbWU+VmljdG9yeSBDb2x1bW48L25hbWU+PC93cHQ+Cjx0cms+PG5hbWU+VGllcmdhcnRlbiBsb29wPC9uYW1lPjx0cmtzZWc+Cjx0cmtwdCBsYXQ9IjUyLjUxNDU0MyIgbG9uPSIxMy4zNTAxMDgiPjxlbGU+MzQ8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjAwOjAwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTYxMDAiIGxvbj0iMTMuMzU1ODAwIj48ZWxlPjM1LjU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjA0OjEwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTc2MDAiIGxvbj0iMTMuMzYyMTAwIj48ZWxlPjM3PC9lbGU+PHRpbWU+MjAyNi0wNS0wMVQwODowODo0MFo8L3RpbWU+PC90cmtwdD4KPHRya3B0IGxhdD0iNTIuNTE2OTAwIiBsb249IjEzLjM2OTUwMCI+PGVsZT4zNjwvZWxlPjx0aW1lPjIwMjYtMDUtMDFUMDg6MTM6MjBaPC90aW1lPjwvdHJrcHQ+Cjx0cmtwdCBsYXQ9IjUyLjUxNjMwMCIgbG9uPSIxMy4zNzc3MDAiPjxlbGU+MzU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjE4OjMwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTM2MDAiIGxvbj0iMTMuMzc2MjAwIj48ZWxlPjM2LjU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjIxOjAwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTEyMDAiIGxvbj0iMTMuMzY2NDAwIj48ZWxlPjM4PC9lbGU+PHRpbWU+MjAyNi0wNS0wMVQwODoyNzowMFo8L3RpbWU+PC90cmtwdD4KPHRya3B0IGxhdD0iNTIuNTEwODAwIiBsb249IjEzLjM1NzkwMCI+PGVsZT4zNjwvZWxlPjx0aW1lPjIwMjYtMDUtMDFUMDg6MzI6MjBaPC90aW1lPjwvdHJrcHQ+Cjx0cmtwdCBsYXQ9IjUyLjUxMjYwMCIgbG9uPSIxMy4zNTE4MDAiPjxlbGU+MzU8L2VsZT48dGltZT4yMDI2LTA1LTAxVDA4OjM2OjMwWjwvdGltZT48L3Rya3B0Pgo8dHJrcHQgbGF0PSI1Mi41MTQ1NDMiIGxvbj0iMTMuMzUwMTA4Ij48ZWxlPjM0PC9lbGU+PHRpbWU+MjAyNi0wNS0wMVQwODozOTowMFo8L3RpbWU+PC90cmtwdD4KPC90cmtzZWc+PC90cms+CjwvZ3B4Pgo="
                    },
                    "to": "kml",
                    "simplify": "douglas-peucker",
                    "toleranceM": 20
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RavingdeskGpsFileConverterResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "RavingdeskGpsFileConverterInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "GPS file (GPX, KML or GeoJSON)",
            "description": "Up to 10 MB and 200,000 points. KMZ files must be unzipped first. Upload as {filename, contentType?, contentBase64}. Max 9766 KiB. Accepts .gpx,.kml,.geojson,.json,application/gpx+xml,application/vnd.google-earth.kml+xml,application/geo+json,application/json,text/xml,application/xml.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "text": {
            "title": "Or paste the file content",
            "type": "string",
            "maxLength": 5000000
          },
          "to": {
            "title": "Convert to",
            "type": "string",
            "enum": [
              "geojson",
              "gpx",
              "kml"
            ],
            "default": "geojson"
          },
          "simplify": {
            "title": "Simplify lines",
            "type": "string",
            "enum": [
              "none",
              "douglas-peucker",
              "visvalingam"
            ],
            "default": "none"
          },
          "toleranceM": {
            "title": "Simplification tolerance",
            "type": "number",
            "minimum": 0.01,
            "maximum": 1000000,
            "default": 5
          },
          "elevationThresholdM": {
            "title": "Elevation noise threshold",
            "description": "Climbs and descents are counted only after the elevation moved this far from the last turning point (GPS heights are noisy).",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 2,
            "x-unit": "m"
          }
        },
        "required": []
      },
      "RavingdeskGpsFileConverterResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "from",
              "to",
              "features",
              "tracks",
              "simplified",
              "file"
            ],
            "properties": {
              "from": {
                "type": "string",
                "enum": [
                  "gpx",
                  "kml",
                  "geojson"
                ]
              },
              "to": {
                "type": "string",
                "enum": [
                  "gpx",
                  "kml",
                  "geojson"
                ]
              },
              "features": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "tracks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "points",
                    "lengthM",
                    "length3dM",
                    "elevationMinM",
                    "elevationMaxM",
                    "gainM",
                    "lossM",
                    "start",
                    "end",
                    "durationS",
                    "averageKmh"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "points": {
                      "type": "integer"
                    },
                    "lengthM": {
                      "type": "number"
                    },
                    "length3dM": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "elevationMinM": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "elevationMaxM": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "gainM": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "lossM": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "start": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "end": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "durationS": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "averageKmh": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              },
              "simplified": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "method",
                  "tolerance",
                  "before",
                  "after"
                ],
                "properties": {
                  "method": {
                    "type": "string"
                  },
                  "tolerance": {
                    "type": "number"
                  },
                  "before": {
                    "type": "integer"
                  },
                  "after": {
                    "type": "integer"
                  }
                }
              },
              "file": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}