{
  "openapi": "3.1.1",
  "info": {
    "title": "Cartograph Meet Halfway (fair meeting point for 2–12 people) API",
    "version": "1.0.0",
    "summary": "Find the fairest meeting point for 2–12 people: geodesic midpoint, minimax and least total distance.",
    "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://cartographmeethalfway.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Find the fairest meeting point for 2–12 people: geodesic midpoint, minimax and least total distance.",
        "description": "Takes two to twelve places (any coordinate format) and finds the geodesic midpoint, the fairest point (the shortest longest trip, i.e. the centre of the smallest circle around everyone on the globe) and the point with the least total distance, with every person’s straight-line distance. With an OpenStreetMap extract it also finds the fairest meeting place by road for walking or cycling. It is the meet-halfway engine of the Cartograph app.\n\nMethodology:\n- All straight-line distances are geodesics on the WGS84 ellipsoid (Karney’s algorithm), not travel times.\n- Midpoint (two people): the point half-way along the geodesic between them.\n- Fairest point: minimises the longest distance anyone travels — the centre of the smallest spherical cap that contains everyone. Found by the Bădoiu–Clarkson iteration (step towards the farthest person by d/(i+1), 400 steps along geodesics) and refined by a compass search down to 1 cm.\n- Least total distance: the geometric median, by Weiszfeld iterations in the azimuthal-equidistant plane of the current estimate. Centre of gravity: the normalised mean of the 3-D unit vectors.\n- By road (optional): each person is snapped to the nearest road node within 1 km, Dijkstra runs from everyone over the extract’s road graph, and the node with the smallest longest road distance (ties: smallest total) is chosen.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "cartograph_meet_halfway",
        "x-owner-app": "cartograph",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CartographMeetHalfwayInput"
              },
              "examples": {
                "example1": {
                  "summary": "Berlin, Hamburg and Munich (straight line)",
                  "value": {
                    "people": "Berlin: 52.5163, 13.3777\nHamburg: 53.5503, 9.9920\nMunich: 48.1374, 11.5755"
                  }
                },
                "example2": {
                  "summary": "Two friends in Vaduz and Schaan, also by road (built-in sample)",
                  "value": {
                    "people": "Anna: 47.1355, 9.5195\nBen: 47.1505, 9.5135",
                    "useRoads": true,
                    "profile": "foot"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartographMeetHalfwayResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "CartographMeetHalfwayInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "people": {
            "title": "People (one per line: “Name: coordinate”)",
            "description": "Coordinates in any format: decimal degrees, DMS, UTM, MGRS, plus codes, geohash. Addresses are not looked up.",
            "type": "string",
            "maxLength": 4000,
            "default": "Berlin: 52.5163, 13.3777\nHamburg: 53.5503, 9.9920\nMunich: 48.1374, 11.5755"
          },
          "useRoads": {
            "title": "Also find the fairest place by road (needs an extract or the Vaduz sample)",
            "type": "boolean",
            "default": false
          },
          "profile": {
            "title": "Travel mode for roads",
            "type": "string",
            "enum": [
              "foot",
              "bike"
            ],
            "default": "foot"
          },
          "file": {
            "title": "OpenStreetMap extract (.osm, .osm.pbf or GeoJSON)",
            "description": "Up to 25 MB. Leave empty to use the built-in Vaduz sample. Download extracts from your own OpenStreetMap export, Geofabrik or BBBike; Cartograph never fetches map data itself. Upload as {filename, contentType?, contentBase64}. Max 24414 KiB. Accepts .osm,.pbf,.geojson,.json,application/xml,text/xml,application/geo+json,application/json,application/octet-stream.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          }
        },
        "required": []
      },
      "CartographMeetHalfwayResult": {
        "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": [
              "people",
              "midpoint",
              "fairest",
              "leastTotal",
              "centroid",
              "byRoad",
              "geojson"
            ],
            "properties": {
              "people": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "label",
                    "lat",
                    "lon"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "lat": {
                      "type": "number"
                    },
                    "lon": {
                      "type": "number"
                    }
                  }
                }
              },
              "midpoint": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "fairest": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "leastTotal": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "centroid": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "byRoad": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM",
                  "profile"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  },
                  "profile": {
                    "type": "string"
                  }
                }
              },
              "geojson": {
                "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"
              }
            }
          }
        }
      }
    }
  }
}