{"openapi":"3.1.0","info":{"title":"Audisory Maps API","version":"1.0.0","summary":"Directions, matrices, isochrones and elevation for Rwanda.","description":"Audisory Maps is a maps platform built for Rwanda: terrain-aware routing with a motorcycle-taxi\nprofile and prepaid billing in RWF.\n\n## Coordinates\n\nEvery coordinate in this API is `[longitude, latitude]` in WGS84 (EPSG:4326), in that order.\n\n## Billing\n\nRequests are paid for with prepaid credits. Every response reports `credits_charged`. When a\nbalance reaches zero the API returns `402 Payment Required` — there is no silent overage. Per\nendpoint costs are documented on each operation.\n\n## Versioning\n\n`/v1` is additive-only: new optional fields and new endpoints may appear, but nothing is removed\nor retyped. Breaking changes ship as `/v2` running in parallel. Deprecations are announced twelve\nmonths ahead with `Deprecation` and `Sunset` headers.\n\n## Attribution\n\nData derived from OpenStreetMap must be displayed with the notice\n\"© OpenStreetMap contributors\".\n","contact":{"name":"Audisory Maps","url":"https://audisory.com"},"license":{"name":"Proprietary","identifier":"LicenseRef-Audisory-Commercial"}},"servers":[{"url":"https://api.uat.audisory.com/v1","description":"Current environment"}],"security":[{"apiKey":[]}],"tags":[{"name":"Routing","description":"Directions, matrices and isochrones over the Rwandan road network."},{"name":"Terrain","description":"Elevation along a path. Rwanda is mountainous; grade drives every ETA."},{"name":"Service","description":"Liveness, readiness and the data release currently being served."}],"paths":{"/directions":{"post":{"operationId":"getDirections","x-displayName":"Directions","x-icon":"Route","tags":["Routing"],"summary":"Route between two or more points","description":"Calculates a route across the Rwandan road network, honouring terrain, surface and\nengine-supported access restrictions. Published local road operations are not yet served.\n\n**Cost:** 1 credit for up to 10 waypoints, plus 1 credit for each additional 10.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectionsRequest"},"examples":{"motoAcrossKigali":{"summary":"Motorcycle taxi from the city centre to Kigali International Airport","value":{"coordinates":[[30.0619,-1.9441],[30.1395,-1.9686]],"profile":"moto"}},"morningPeak":{"summary":"Same route with an explicit departure time","value":{"coordinates":[[30.0619,-1.9441],[30.1395,-1.9686]],"profile":"moto","departure_time":"2026-09-02T07:15:00+02:00"}}}}}},"responses":{"200":{"description":"A route was found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectionsResponse"},"examples":{"motoAcrossKigali":{"summary":"Route with the elevation profile that distinguishes this API","value":{"request_id":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","credits_charged":1,"data_version":"2026-09-02T03","profile":"moto","routes":[{"id":0,"summary":{"distance_m":12480,"duration_s":1320},"elevation":{"ascent_m":142,"descent_m":38,"max_grade":0.09},"traffic_confidence":"low","geometry":"zpq}Ao~ihE...","legs":[{"distance_m":12480,"duration_s":1320}]}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unroutable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/matrix":{"post":{"operationId":"getMatrix","x-displayName":"Matrices","x-icon":"Grid2X2","tags":["Routing"],"summary":"Travel time and distance between many points","description":"Computes distances and durations between every source and every target. Useful for dispatch,\nnearest-driver selection and delivery clustering.\n\n**Cost:** 1 credit per 10 elements, where elements = sources × targets. The cost driver is\nelements, not calls.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixRequest"},"examples":{"nearestRider":{"summary":"Three riders against one pickup point","value":{"sources":[[30.0619,-1.9441],[30.108,-1.954],[30.043,-1.939]],"targets":[[30.122,-1.942]],"profile":"moto"}}}}}},"responses":{"200":{"description":"The matrix was computed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixResponse"},"examples":{"nearestRider":{"summary":"Durations in seconds, distances in metres","value":{"request_id":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8TA","credits_charged":1,"data_version":"2026-09-02T03","profile":"moto","durations":[[1180],[620],[1450]],"distances":[[9200],[4800],[11300]]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/isochrone":{"post":{"operationId":"getIsochrone","x-displayName":"Isochrones","x-icon":"ScanLine","tags":["Routing"],"summary":"Reachable area within a time or distance budget","description":"Returns polygons covering everywhere reachable from a point within the given budgets. The\nbasis for delivery zones and catchment analysis.\n\n**Cost:** 5 credits. Isochrones are an expensive graph traversal.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IsochroneRequest"},"examples":{"deliveryZone":{"summary":"Fifteen and thirty minute delivery zones by motorcycle","value":{"origin":[30.0619,-1.9441],"profile":"moto","contours":[{"time_minutes":15},{"time_minutes":30}]}}}}}},"responses":{"200":{"description":"Isochrone polygons, as a GeoJSON feature collection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IsochroneResponse"},"examples":{"deliveryZone":{"summary":"One feature per contour","value":{"request_id":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8TB","credits_charged":5,"data_version":"2026-09-02T03","profile":"moto","features":{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"contour":15,"unit":"minutes"},"geometry":{"type":"Polygon","coordinates":[[[30.05,-1.94],[30.08,-1.94],[30.08,-1.96],[30.05,-1.94]]]}}]}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/elevation":{"post":{"operationId":"getElevation","x-displayName":"Elevation","x-icon":"Mountain","tags":["Terrain"],"summary":"Elevation along a path","description":"Samples surface elevation at each supplied coordinate from Copernicus DEM GLO-30 (EGM2008).\nThe surface model includes vegetation and buildings; it is not surveyed road height.\n\n**Cost:** 1 credit per 100 points.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ElevationRequest"},"examples":{"acrossKigali":{"summary":"Three points along a Kigali ridge","value":{"coordinates":[[30.0619,-1.9441],[30.09,-1.937],[30.122,-1.942]]}}}}}},"responses":{"200":{"description":"Elevations in metres above sea level, in the order supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ElevationResponse"},"examples":{"acrossKigali":{"summary":"Kigali sits between roughly 1,400 and 1,650 metres","value":{"request_id":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8TC","credits_charged":1,"data_version":"2026-09-02T03","elevations_m":[1567,1489,1442]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"Audisory API key","description":"Send your key as `Authorization: Bearer sk_live_…`.\n\nSecret keys (`sk_`) are for server-side use and must never be exposed in a browser or a\nmobile binary. Browser or mobile use of secret keys is unsupported. Use your server to call these endpoints.\n"}},"schemas":{"Coordinate":{"type":"array","description":"A WGS84 position as `[longitude, latitude]`, in that order.","minItems":2,"maxItems":2,"prefixItems":[{"type":"number","format":"double","description":"Longitude in decimal degrees.","minimum":-180,"maximum":180},{"type":"number","format":"double","description":"Latitude in decimal degrees.","minimum":-90,"maximum":90}],"x-go-type":"[]float64","examples":[[30.0619,-1.9441]]},"Profile":{"type":"string","description":"The vehicle and behaviour model used for costing. `moto` is the motorcycle-taxi profile and\nthe one most Rwandan traffic actually uses.\n","enum":["moto","car","truck","bicycle","pedestrian","pedestrian_loaded"],"default":"car","examples":["moto"]},"Envelope":{"type":"object","description":"Fields present on every successful response.","required":["request_id","credits_charged","data_version"],"properties":{"request_id":{"type":"string","description":"Unique id for this request. Quote it in any support conversation.","maxLength":64,"examples":["req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9"]},"credits_charged":{"type":"integer","description":"Credits deducted from the account balance for this request.","minimum":0,"examples":[1]},"data_version":{"type":"string","description":"The data release that answered this request.","maxLength":32,"examples":["2026-09-02T03"]}}},"DirectionsRequest":{"type":"object","required":["coordinates"],"properties":{"coordinates":{"type":"array","description":"Waypoints in visiting order, starting with the origin.","items":{"$ref":"#/components/schemas/Coordinate"},"minItems":2,"maxItems":50},"profile":{"$ref":"#/components/schemas/Profile"},"departure_time":{"type":"string","format":"date-time","description":"Departure time. Supplying it applies time-conditioned restrictions such as bus lanes and\nmarket-day closures. Defaults to now.\n","examples":["2026-09-02T07:15:00+02:00"]},"alternatives":{"type":"integer","description":"Number of alternative routes to return in addition to the best one.","minimum":0,"maximum":3,"default":1},"geometry_format":{"type":"string","description":"Encoding of the returned route geometry.","enum":["polyline6","geojson"],"default":"polyline6"},"include_steps":{"type":"boolean","description":"Include turn-by-turn maneuver steps in each route leg.","default":false}}},"RouteSummary":{"type":"object","description":"Distance and estimated duration for a route.","required":["distance_m","duration_s"],"properties":{"average_speed_kph":{"x-omitempty":false,"type":["number","null"],"format":"double","minimum":0,"description":"Estimated average travel speed including delays; null when duration is zero."},"distance_m":{"type":"number","format":"double","description":"Total distance in metres.","minimum":0,"examples":[12480]},"duration_s":{"type":"number","format":"double","description":"Estimated travel time in seconds.","minimum":0,"examples":[1320]}}},"ElevationProfile":{"type":"object","description":"Cumulative climb and descent along the route. In Rwanda this is the difference between a\ncredible ETA and a plausible-looking wrong one.\n","required":["ascent_m","descent_m","max_grade"],"properties":{"ascent_m":{"type":"number","format":"double","description":"Total metres climbed.","minimum":0,"examples":[142]},"descent_m":{"type":"number","format":"double","description":"Total metres descended.","minimum":0,"examples":[38]},"max_grade":{"type":"number","format":"double","description":"Maximum absolute sampled gradient, as a ratio (0.09 is a 9% grade).","examples":[0.09]}}},"RouteLeg":{"type":"object","required":["distance_m","duration_s"],"properties":{"average_speed_kph":{"x-omitempty":false,"type":["number","null"],"format":"double","minimum":0,"description":"Estimated average travel speed including delays; null when duration is zero."},"distance_m":{"type":"number","format":"double","minimum":0,"description":"Distance of this leg in metres.","examples":[12480]},"duration_s":{"type":"number","format":"double","minimum":0,"description":"Duration of this leg in seconds.","examples":[1320]}}},"RouteStep":{"type":"object","required":["instruction","distance_m","duration_s"],"properties":{"instruction":{"type":"string","description":"Human-readable maneuver instruction from the routing engine.","maxLength":1000,"examples":["Turn right onto KN 3 Rd"]},"distance_m":{"type":"number","format":"double","minimum":0,"description":"Distance covered by this maneuver in metres."},"duration_s":{"type":"number","format":"double","minimum":0,"description":"Estimated duration of this maneuver in seconds."},"road_name":{"type":"string","description":"Road name when present in the source data.","maxLength":500},"road_class":{"type":"string","description":"Road class when present in the source data.","maxLength":100}}},"Route":{"type":"object","required":["id","summary","geometry","legs"],"properties":{"speed_model_version":{"type":"string","maxLength":100,"description":"Version of the terrain speed model used by the routing engine."},"speed_model_status":{"type":"string","enum":["provisional","calibrated"],"description":"Whether the speed model has been calibrated against representative trips."},"id":{"type":"integer","description":"Stable identifier within this response; 0 for the best route. uses array indexing.","examples":[0]},"summary":{"$ref":"#/components/schemas/RouteSummary"},"elevation":{"$ref":"#/components/schemas/ElevationProfile"},"traffic_confidence":{"type":"string","description":"How much observed traffic data informed this route ETA.","enum":["none","low","medium","high"]},"operations_applied":{"type":"boolean","description":"Whether a reviewed operational closure or slowdown informed this route."},"geometry":{"type":"string","description":"Encoded polyline6 or serialised GeoJSON geometry.","maxLength":1000000},"legs":{"type":"array","description":"One leg per consecutive pair of waypoints.","items":{"$ref":"#/components/schemas/RouteLegWithSteps"},"maxItems":50}}},"RouteLegWithSteps":{"allOf":[{"$ref":"#/components/schemas/RouteLeg"},{"type":"object","properties":{"steps":{"type":"array","description":"Turn-by-turn maneuvers, present when include_steps is true.","items":{"$ref":"#/components/schemas/RouteStep"},"maxItems":1000}}}]},"DirectionsResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["profile","routes"],"properties":{"profile":{"$ref":"#/components/schemas/Profile"},"routes":{"type":"array","description":"Best route first, followed by requested alternatives.","items":{"$ref":"#/components/schemas/Route"},"minItems":1,"maxItems":4}}}]},"MatrixRequest":{"type":"object","required":["sources","targets"],"properties":{"sources":{"type":"array","description":"Origins.","items":{"$ref":"#/components/schemas/Coordinate"},"minItems":1,"maxItems":100},"targets":{"type":"array","description":"Destinations.","items":{"$ref":"#/components/schemas/Coordinate"},"minItems":1,"maxItems":100},"profile":{"$ref":"#/components/schemas/Profile"},"departure_time":{"type":"string","format":"date-time","description":"Departure time, applied to every element.","examples":["2026-09-02T07:15:00+02:00"]}}},"MatrixResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["profile","durations","distances"],"properties":{"profile":{"$ref":"#/components/schemas/Profile"},"durations":{"type":"array","description":"Durations in seconds, indexed `[source][target]`. A `null` element means that pair\nis unroutable.\n","items":{"type":"array","items":{"type":["number","null"],"format":"double","minimum":0},"maxItems":100},"maxItems":100},"distances":{"type":"array","description":"Distances in metres, indexed `[source][target]`.","items":{"type":"array","items":{"type":["number","null"],"format":"double","minimum":0},"maxItems":100},"maxItems":100}}}]},"Contour":{"type":"object","description":"One reachability budget. Supply exactly one of `time_minutes` or `distance_km`.","properties":{"time_minutes":{"type":"number","format":"double","description":"Travel-time budget in minutes.","minimum":1,"maximum":120,"examples":[15]},"distance_km":{"type":"number","format":"double","description":"Travel-distance budget in kilometres.","minimum":0.5,"maximum":100,"examples":[5]}}},"IsochroneRequest":{"type":"object","required":["origin","contours"],"properties":{"origin":{"$ref":"#/components/schemas/Coordinate"},"profile":{"$ref":"#/components/schemas/Profile"},"contours":{"type":"array","description":"Up to four budgets, returned as nested polygons.","items":{"$ref":"#/components/schemas/Contour"},"minItems":1,"maxItems":4},"departure_time":{"type":"string","format":"date-time","description":"Departure time, so time-conditioned restrictions apply.","examples":["2026-09-02T07:15:00+02:00"]}}},"IsochroneResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["profile","features"],"properties":{"profile":{"$ref":"#/components/schemas/Profile"},"features":{"type":"object","description":"A GeoJSON `FeatureCollection`, one feature per contour.","required":["type","features"],"properties":{"type":{"type":"string","description":"Always `FeatureCollection`.","const":"FeatureCollection"},"features":{"type":"array","description":"One polygon feature per requested contour.","items":{"type":"object","description":"A GeoJSON Feature whose geometry is a Polygon or MultiPolygon."},"maxItems":4}}}}}]},"ElevationRequest":{"type":"object","required":["coordinates"],"properties":{"coordinates":{"type":"array","description":"Points to sample, in order.","items":{"$ref":"#/components/schemas/Coordinate"},"minItems":1,"maxItems":1000}}},"ElevationResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","required":["elevations_m"],"properties":{"elevations_m":{"type":"array","description":"Metres above sea level, in the order the coordinates were supplied. A `null` means\nthe point falls outside our elevation coverage.\n","items":{"type":["number","null"],"format":"double"},"maxItems":1000}}}]},"Problem":{"type":"object","description":"An RFC 9457 problem response.","required":["type","title","status","detail","instance","errors"],"properties":{"type":{"type":"string","format":"uri","maxLength":500,"description":"URI identifying the problem category."},"title":{"type":"string","maxLength":200,"description":"Short human-readable problem summary."},"status":{"type":"integer","minimum":400,"maximum":599,"description":"HTTP response status code."},"detail":{"type":"string","maxLength":1000,"description":"Explanation of this failure."},"instance":{"type":"string","maxLength":64,"description":"Request identifier for support and log correlation."},"errors":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/FieldError"},"description":"Field-level validation failures, empty when not applicable."}}},"FieldError":{"type":"object","required":["field","message"],"properties":{"field":{"type":"string","maxLength":200,"description":"Name or path of the invalid input field."},"message":{"type":"string","maxLength":200,"description":"Human-readable validation failure."}},"description":"One field-level validation failure."}},"responses":{"BadRequest":{"description":"The request was malformed or failed validation.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"invalidCoordinate":{"summary":"A coordinate was supplied as latitude/longitude instead of longitude/latitude","value":{"type":"https://docs.audisory.example/errors/invalid-request","title":"Invalid request","status":400,"detail":"One or more coordinates are outside the valid range.","instance":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","errors":[{"field":"/coordinates/0/0","message":"longitude must be between -180 and 180"}]}}}}}},"Unauthorized":{"description":"The API key is missing, malformed or revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"missingKey":{"summary":"No Authorization header was sent","value":{"type":"https://docs.audisory.example/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Provide your key as \"Authorization: Bearer sk_live_…\".","instance":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","errors":[]}}}}}},"Forbidden":{"description":"The key is valid but may not make this request: public keys cannot reach compute\nendpoints, and keys of a suspended account are refused until staff restore it.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"suspended":{"summary":"The owning account is suspended","value":{"type":"https://docs.audisory.example/errors/account-suspended","title":"Account suspended","status":403,"detail":"This account is suspended. Contact Audisory support to restore access.","instance":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","errors":[]}}}}}},"PaymentRequired":{"description":"The account has run out of credits, or the request would exceed a spend cap. Spend caps are\non by default; this response is the product working as intended, not a fault.\n","headers":{"X-Credits-Remaining":{"description":"Credits left on the account.","schema":{"type":"integer","minimum":0}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"exhausted":{"summary":"Balance reached zero","value":{"type":"https://docs.audisory.example/errors/insufficient-credits","title":"Insufficient credits","status":402,"detail":"Balance is 0 credits; this request costs 5.","instance":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","errors":[]}}}}}},"Unroutable":{"description":"The request was valid but no route exists between the given points.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"noPath":{"summary":"One endpoint is not connected to the road network","value":{"type":"https://docs.audisory.example/errors/unroutable","title":"No route found","status":422,"detail":"No path exists between the supplied coordinates for profile \"moto\".","instance":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","errors":[]}}}}}},"TooManyRequests":{"description":"The request rate limit for this key was exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer","minimum":0}},"X-RateLimit-Reset":{"description":"Unix timestamp at which the window resets.","schema":{"type":"integer","minimum":0}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"throttled":{"summary":"Burst allowance exhausted","value":{"type":"https://docs.audisory.example/errors/rate-limited","title":"Too many requests","status":429,"detail":"Rate limit of 50 requests per second exceeded.","instance":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","errors":[]}}}}}},"ServiceUnavailable":{"description":"A backend is temporarily unavailable. Retry with backoff.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"backendDown":{"summary":"The routing engine is not answering","value":{"type":"https://docs.audisory.example/errors/service-unavailable","title":"Service unavailable","status":503,"detail":"The routing backend did not respond. Please retry.","instance":"req_01JBQ7Z9Y4K2M3N4P5Q6R7S8T9","errors":[]}}}}}}}}}