{"openapi":"3.1.0","info":{"title":"Ubhub Location Service API","description":"\nGeocoding, reverse geocoding and routing for Mongolia.\n\n**Authentication.** Every `/v1` request needs an API key in the `Authorization` header:\n`Authorization: Bearer YOUR_API_KEY`. Create keys in the developer console.\n\n**Errors.** Every error uses one envelope:\n`{\"error\": {\"code\": \"INVALID_REQUEST\", \"message\": \"...\", \"details\": {...}}}`.\n\n**Rate limits.** Each key has a per-minute limit (100 by default). Every response carries\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`.\n\n**Versioning.** `/v1` never receives breaking changes. New optional fields may be added to responses.\n","version":"1.0.0"},"servers":[{"url":"https://developers.ubhub.mn","description":"Public API"}],"paths":{"/v1/geocode":{"get":{"tags":["Geocoding"],"summary":"Geocode a place, address or coordinate","description":"One geocoding endpoint in two directions:\n\n- **Forward** — pass `q` (a place name or address). Results are ranked: exact name matches first, then names starting with the query, then names containing every word.\n- **Reverse** — pass `lat` and `lon`. The single nearest meaningful location is returned, with `type` set to the match kind (`address`, `place`, `street`) and `distance_meters` set.\n\nBoth directions return the same shape: a `results` array (empty when nothing matched, which is not an error). Send either `q` or both `lat` and `lon`, not both and not neither.","operationId":"geocode","security":[{"ApiKey":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":200},{"type":"null"}],"description":"Forward geocoding: place name or address. Latin and Cyrillic are both supported.","examples":["Sukhbaatar Square"],"title":"Q"},"description":"Forward geocoding: place name or address. Latin and Cyrillic are both supported."},{"name":"lat","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":90,"minimum":-90},{"type":"null"}],"description":"Reverse geocoding: latitude (WGS84).","examples":[47.9184],"title":"Lat"},"description":"Reverse geocoding: latitude (WGS84)."},{"name":"lon","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":180,"minimum":-180},{"type":"null"}],"description":"Reverse geocoding: longitude (WGS84).","examples":[106.9177],"title":"Lon"},"description":"Reverse geocoding: longitude (WGS84)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"Forward geocoding: maximum number of results.","default":5,"title":"Limit"},"description":"Forward geocoding: maximum number of results."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}},"400":{"description":"400 INVALID_REQUEST","content":{"application/json":{"example":{"error":{"code":"INVALID_REQUEST","message":"Invalid value for 'lat': must be between -90 and 90.","details":{"field":"lat"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"401 INVALID_API_KEY","content":{"application/json":{"example":{"error":{"code":"INVALID_API_KEY","message":"The API key is missing or invalid."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"403 API_KEY_REVOKED or ENDPOINT_NOT_ALLOWED","content":{"application/json":{"example":{"error":{"code":"API_KEY_REVOKED","message":"This API key has been revoked."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"408":{"description":"408 REQUEST_TIMEOUT","content":{"application/json":{"example":{"error":{"code":"REQUEST_TIMEOUT","message":"The data service did not respond in time."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"429 RATE_LIMIT_EXCEEDED","content":{"application/json":{"example":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Too many requests."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"500 INTERNAL_ERROR","content":{"application/json":{"example":{"error":{"code":"INTERNAL_ERROR","message":"Internal server error."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"502 UPSTREAM_ERROR","content":{"application/json":{"example":{"error":{"code":"UPSTREAM_ERROR","message":"The upstream data service returned an error."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"503 SERVICE_UNAVAILABLE","content":{"application/json":{"example":{"error":{"code":"SERVICE_UNAVAILABLE","message":"The service is temporarily unavailable."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/route":{"get":{"tags":["Routing"],"summary":"Calculate a route between two points","description":"Returns the fastest route for the chosen travel mode, with its distance, estimated travel time and a GeoJSON LineString. Origin and destination are snapped to the nearest road the mode can use; the snapped points are returned in `waypoints`.","operationId":"route","security":[{"ApiKey":[]}],"parameters":[{"name":"origin","in":"query","required":true,"schema":{"type":"string","maxLength":64,"description":"Longitude and latitude separated by a comma, in that order (GeoJSON order).","examples":["106.9177,47.9184"],"title":"Origin"},"description":"Longitude and latitude separated by a comma, in that order (GeoJSON order)."},{"name":"destination","in":"query","required":true,"schema":{"type":"string","maxLength":64,"description":"Longitude and latitude separated by a comma, in that order (GeoJSON order).","examples":["106.9057,47.9220"],"title":"Destination"},"description":"Longitude and latitude separated by a comma, in that order (GeoJSON order)."},{"name":"mode","in":"query","required":false,"schema":{"$ref":"#/components/schemas/TravelMode","description":"Travel mode.","default":"driving"},"description":"Travel mode."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteResponse"}}}},"400":{"description":"400 INVALID_REQUEST","content":{"application/json":{"example":{"error":{"code":"INVALID_REQUEST","message":"Invalid value for 'lat': must be between -90 and 90.","details":{"field":"lat"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"401 INVALID_API_KEY","content":{"application/json":{"example":{"error":{"code":"INVALID_API_KEY","message":"The API key is missing or invalid."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"403 API_KEY_REVOKED or ENDPOINT_NOT_ALLOWED","content":{"application/json":{"example":{"error":{"code":"API_KEY_REVOKED","message":"This API key has been revoked."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"404 NOT_FOUND","content":{"application/json":{"example":{"error":{"code":"NOT_FOUND","message":"No result was found for this request."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"408":{"description":"408 REQUEST_TIMEOUT","content":{"application/json":{"example":{"error":{"code":"REQUEST_TIMEOUT","message":"The data service did not respond in time."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"429 RATE_LIMIT_EXCEEDED","content":{"application/json":{"example":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Too many requests."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"500 INTERNAL_ERROR","content":{"application/json":{"example":{"error":{"code":"INTERNAL_ERROR","message":"Internal server error."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"502 UPSTREAM_ERROR","content":{"application/json":{"example":{"error":{"code":"UPSTREAM_ERROR","message":"The upstream data service returned an error."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"503 SERVICE_UNAVAILABLE","content":{"application/json":{"example":{"error":{"code":"SERVICE_UNAVAILABLE","message":"The service is temporarily unavailable."}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/health":{"get":{"tags":["Health"],"summary":"Liveness probe","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/health/ready":{"get":{"tags":["Health"],"summary":"Readiness probe","operationId":"ready_health_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}},"503":{"description":"A required dependency is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}}}}}},"components":{"schemas":{"ErrorDetail":{"properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_API_KEY","API_KEY_REVOKED","ENDPOINT_NOT_ALLOWED","NOT_FOUND","REQUEST_TIMEOUT","RATE_LIMIT_EXCEEDED","INTERNAL_ERROR","UPSTREAM_ERROR","SERVICE_UNAVAILABLE"],"title":"Code","description":"Stable, machine-readable error code."},"message":{"type":"string","title":"Message","description":"Human-readable explanation. Wording may change; do not parse it."},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details","description":"Optional structured context, for example the offending `field`."},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"Identifies this request in the server logs (same as the `X-Request-ID` header). Quote it when you contact support."}},"type":"object","required":["code","message"],"title":"ErrorDetail"},"ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}},"type":"object","required":["error"],"title":"ErrorResponse"},"GeocodeResponse":{"properties":{"query":{"type":"string","title":"Query","description":"The text searched, or `lat,lon` for a reverse lookup.","examples":["Sukhbaatar Square"]},"results":{"items":{"$ref":"#/components/schemas/GeocodeResult"},"type":"array","title":"Results"},"count":{"type":"integer","title":"Count","description":"Number of results returned.","examples":[1]}},"type":"object","required":["query","results","count"],"title":"GeocodeResponse"},"GeocodeResult":{"properties":{"id":{"type":"string","title":"Id","description":"Opaque, stable identifier of the place.","examples":["plc_123"]},"name":{"type":"string","title":"Name","examples":["Sukhbaatar Square"]},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address","examples":["Ulaanbaatar, Mongolia"]},"latitude":{"type":"number","title":"Latitude","examples":[47.9184]},"longitude":{"type":"number","title":"Longitude","examples":[106.9177]},"type":{"type":"string","title":"Type","description":"Place category, or for reverse lookups the match kind (`address`, `place`, `street`).","examples":["place"]},"distance_meters":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Distance Meters","description":"Only for reverse lookups: distance in metres from the requested point to this match.","examples":[12.4]}},"type":"object","required":["id","name","latitude","longitude","type"],"title":"GeocodeResult"},"HealthResponse":{"properties":{"status":{"type":"string","const":"ok","title":"Status"}},"type":"object","required":["status"],"title":"HealthResponse"},"LineString":{"properties":{"type":{"type":"string","const":"LineString","title":"Type","default":"LineString"},"coordinates":{"items":{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},"type":"array","title":"Coordinates","description":"GeoJSON positions as `[longitude, latitude]` pairs.","examples":[[[106.9177,47.9184],[106.9057,47.922]]]}},"type":"object","required":["coordinates"],"title":"LineString"},"Location":{"properties":{"latitude":{"type":"number","title":"Latitude","examples":[47.9184]},"longitude":{"type":"number","title":"Longitude","examples":[106.9177]}},"type":"object","required":["latitude","longitude"],"title":"Location"},"ReadinessResponse":{"properties":{"status":{"type":"string","enum":["ok","degraded","unavailable"],"title":"Status"},"checks":{"additionalProperties":{"type":"string","enum":["ok","fail","not_configured"]},"type":"object","title":"Checks"}},"type":"object","required":["status","checks"],"title":"ReadinessResponse"},"Route":{"properties":{"distance_meters":{"type":"number","title":"Distance Meters","examples":[4200.0]},"duration_seconds":{"type":"number","title":"Duration Seconds","examples":[620.0]},"geometry":{"$ref":"#/components/schemas/LineString"}},"type":"object","required":["distance_meters","duration_seconds","geometry"],"title":"Route"},"RouteResponse":{"properties":{"route":{"$ref":"#/components/schemas/Route"},"mode":{"$ref":"#/components/schemas/TravelMode"},"waypoints":{"items":{"$ref":"#/components/schemas/Waypoint"},"type":"array","title":"Waypoints","description":"Snapped origin and destination, in that order."}},"type":"object","required":["route","mode","waypoints"],"title":"RouteResponse"},"TravelMode":{"type":"string","enum":["driving","walking"],"title":"TravelMode"},"Waypoint":{"properties":{"input":{"$ref":"#/components/schemas/Location","description":"The coordinate you sent."},"location":{"$ref":"#/components/schemas/Location","description":"The point on the road network the route starts or ends at."},"snap_distance_meters":{"type":"number","title":"Snap Distance Meters","description":"Distance between `input` and `location`."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Name of the road at the snapped location."}},"type":"object","required":["input","location","snap_distance_meters"],"title":"Waypoint"}},"securitySchemes":{"ApiKey":{"type":"http","description":"Send your API key in the `Authorization` header: `Authorization: Bearer YOUR_API_KEY`.","scheme":"bearer","bearerFormat":"geo_…"}}},"tags":[{"name":"Geocoding","description":"Place names and addresses to coordinates."},{"name":"Reverse geocoding","description":"Coordinates to addresses."},{"name":"Routing","description":"Routes, distances and travel times."},{"name":"Health","description":"Service health probes."}]}