How to Handle Unreachable Waypoints and Routing Errors
- API or service
- Routing API
- Task
- Waypoints → error type → fix input, move a pin, change mode, or retry
- Examples
- Difficulty
- Intermediate
- Time
- 12 min
Handle unreachable waypoints and routing errors
Your application sends waypoints to the Geoapify Routing API, and the request fails or returns a route that does not match what the user picked. A pin may be in a lake, on an island without a bridge, too far away for a bicycle, or typed in the wrong coordinate order. You need to tell these cases apart and choose the next step: fix the input, ask the user to move a pin, suggest another travel mode, or retry later.
The Routing API reports these failures as JSON error responses with statusCode, error, and message. Most routing failures use HTTP 400, so the message text tells you which case you have. A successful HTTP 200 response can still contain a waypoint that the API moved several kilometers to the nearest usable road or path.
Task flow: waypoints → Routing API → HTTP status and message → classify the failure → fix input, move a pin, change the mode, or retry → check a successful route for moved waypoints.
Classify routing errors
The Routing API returns an error as a JSON object with statusCode, error, and message. Read the message together with the status code to find the cause:
{
"statusCode": 400,
"error": "Bad Request",
"message": "No path could be found for input"
}
The waypoint and path failures tested in this guide return HTTP 400, so the status code alone cannot separate a typing mistake from an island without a bridge. Read message as well. The following messages were returned by the Routing API for the requests in this guide:
| Status | message |
Cause | What to do |
|---|---|---|---|
| 400 | "waypoints" is required |
The waypoints parameter is missing. |
Fix the request. |
| 400 | Insufficient number of locations provided |
Only one waypoint was sent. | Ask for a second waypoint. |
| 400 | Failed to parse location |
A waypoint cannot be read as coordinates, for example latitude 91.5. |
Validate coordinates before sending. |
| 400 | "waypoints[0]" does not match any of the allowed types |
A waypoint is text, such as a city name, instead of latitude,longitude. |
Geocode the address first. |
| 400 | Too long distance between locations. … |
Two consecutive waypoints are farther apart than the limit for the travel mode. | Add intermediate waypoints, split the trip, or choose another mode. |
| 400 | No suitable edges near location. Please check waypoint coordinate order (lat/lon). |
A waypoint is too far from any road or path that the travel mode can use, or latitude and longitude are swapped. | Check the coordinate order, then ask the user to move the pin or try another mode. |
| 400 | No path could be found for input |
Every waypoint is near a usable road, but no connection exists for this mode and these avoid rules. |
Relax avoid rules, try another mode, or choose another destination. |
| 401 | Invalid apiKey |
The API key is missing or invalid. | Fix the key configuration. |
| 429 | Quota exceeded |
The rate limit or quota is exceeded. | Retry later with backoff. |
| 500 | Request failed, please try again later |
A server-side failure. | Retry a limited number of times; contact Geoapify if it repeats. |
The 429 and 500 bodies are the standard gateway responses from the Routing API interactive reference.
Group the messages by who can fix them:
- Request input (
"waypoints" is required,Insufficient number of locations provided,Failed to parse location,does not match any of the allowed types): a bug or missing validation in your code. Sending the same request again returns the same error. - Waypoint placement (
No suitable edges near location): the user, or the data source, picked a point that the selected mode cannot reach. - Trip shape (
Too long distance between locations,No path could be found for input): the points are valid, but the trip does not work for this mode, distance, oravoidrule. - Temporary (429, 500): the same request may succeed later.
Do not retry a 400 response unchanged. Change the input, the mode, or the avoid rules first.
Fix invalid waypoint input
The examples on this page route from 65 West Jackson Boulevard in the Chicago Loop (41.8781,-87.6298) to 754 East Grand Avenue near Navy Pier (41.8919,-87.6051). Each waypoint is latitude,longitude; waypoints are separated by |. At least two waypoints are required.
All of these requests return HTTP 400. They fail the same way every time, so fix the input in your code instead of retrying.
Missing waypoints
https://api.geoapify.com/v1/routing?mode=drive&format=json&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "\"waypoints\" is required"
}
Only one waypoint
https://api.geoapify.com/v1/routing?waypoints=41.8781,-87.6298&mode=drive&format=json&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "Insufficient number of locations provided"
}
This usually happens when the user has selected the start but not yet the destination. Wait for the second waypoint instead of calling the API.
Latitude out of range
The first waypoint has latitude 91.5; valid latitudes are from -90 to 90:
https://api.geoapify.com/v1/routing?waypoints=91.5,-87.6298|41.8919,-87.6051&mode=drive&format=json&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "Failed to parse location"
}
Address text instead of coordinates
https://api.geoapify.com/v1/routing?waypoints=Chicago|Evanston&mode=drive&format=json&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "\"waypoints[0]\" does not match any of the allowed types"
}
The Routing API does not geocode. Convert each address to coordinates with the Geocoding API or Address Autocomplete first, then send the coordinates.
Longitude and latitude swapped
The same two Chicago points, sent as longitude,latitude:
https://api.geoapify.com/v1/routing?waypoints=-87.6298,41.8781|-87.6051,41.8919&mode=drive&format=json&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "No suitable edges near location. Please check waypoint coordinate order (lat/lon)."
}
The API reads -87.6298 as a latitude, which is a point in Antarctica, so no road is found. The coordinates are valid numbers, so this is not a parsing error. Map libraries and GeoJSON use [longitude, latitude], while the waypoints parameter uses latitude,longitude. Either swap the values or use the lonlat: prefix, for example waypoints=lonlat:-87.6298,41.8781|lonlat:-87.6051,41.8919.
The API currently returns the same message for a longitude outside -180 to 180, for example 41.8781,-187.6298, instead of a parsing error. Validate both ranges in your code before sending the request:
function isValidLatLon(lat, lon) {
return Number.isFinite(lat) && Number.isFinite(lon) &&
lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180;
}
A range check does not catch swapped values when both numbers are between -90 and 90, as in Chicago or anywhere east of the 90th meridian west. Keep coordinates in named fields (lat, lon) throughout your code instead of positional arrays.
Stay within distance limits
Some travel modes limit the distance between two consecutive waypoints. A bicycle route from 65 West Jackson Boulevard, Chicago, IL, to 300 4th Street South, Minneapolis, MN:
https://api.geoapify.com/v1/routing?waypoints=41.8781,-87.6298|44.9778,-93.2650&mode=bicycle&format=json&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "Too long distance between locations. Distance should not exceed 300000 meters for a regular API call, and 600000 meters for an asynchronous batch API call. Estimated distance is 570630 meter(s)."
}
The message contains the limit and the estimated distance. The estimated distance, 570,630 m, is the straight-line (great-circle) distance between the two waypoints, not the length of a cycling route. The API checks it before calculating a route, so the request fails quickly.
The check applies to each pair of consecutive waypoints, not to the whole trip. The same ride with Milwaukee (43.0389,-87.9065) and Green Bay (44.5192,-88.0198) as stops keeps each pair under 300 km and returns HTTP 200 with a 357,362 m route:
https://api.geoapify.com/v1/routing?waypoints=41.8781,-87.6298|43.0389,-87.9065|44.5192,-88.0198&mode=bicycle&format=json&apiKey=YOUR_API_KEY
Because the limit uses straight-line distance, the returned route can be longer than the limit. Chicago to Green Bay alone is about 293 km in a straight line and returned a 356,941 m bicycle route.
Observed limits per mode
For a pair of points 570,630 m apart (Chicago–Minneapolis) and a pair 131,056 m apart (Chicago–Milwaukee), the API currently returns the following limits in the message:
| Mode | Regular call | Asynchronous batch call |
|---|---|---|
walk, hike |
100,000 m | 200,000 m |
bicycle, scooter |
300,000 m | 600,000 m |
mountain_bike |
500,000 m | 1,000,000 m |
road_bike |
1,000,000 m | 1,000,000 m |
drive and motorcycle requests did not hit a distance limit in these tests: New York City (40.7128,-74.0060) to Los Angeles (34.0522,-118.2437) in drive mode, about 3,936 km in a straight line, returned a 4,496,027 m route, and Chicago to Denver (39.7392,-104.9903) in motorcycle mode returned a 1,617,046 m route.
The Routing API interactive reference documents the walk and hike limit. Treat the other values as current behavior, and show the limit from the message to the user rather than a hardcoded number.
What to do
- Add intermediate waypoints or split the trip into stages, for example one stage per day of a bike tour, so that each consecutive pair is under the limit.
- Use the Batch API with
"api": "/v1/routing"when the batch limit stated in the message is enough and the result does not have to be immediate. - Offer a mode with a higher limit only when it matches the user's intent, for example
driveinstead ofwalkfor a 130 km trip. - Check the straight-line distance in your code before sending the request when you keep a per-mode limit in configuration. It avoids a request that will fail, and the API error remains the final check.
Handle waypoints far from roads
Before calculating a route, the Routing API moves each waypoint to a nearby road or path that the selected mode can use. When there is none close enough, the request fails.
A user starts at 65 West Jackson Boulevard in the Chicago Loop and drops the destination pin in Lake Michigan, about 7 km off the lakefront (41.95,-87.55):
https://api.geoapify.com/v1/routing?waypoints=41.8781,-87.6298|41.95,-87.55&mode=drive&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "No suitable edges near location. Please check waypoint coordinate order (lat/lon)."
}
"Edges" are road or path segments in the routing graph. The message does not say which waypoint failed. With three waypoints and the lake point in the middle or first position, the API currently returns the same message.
The search distance depends on the mode
The same two points in walk mode return HTTP 200:
https://api.geoapify.com/v1/routing?waypoints=41.8781,-87.6298|41.95,-87.55&mode=walk&apiKey=YOUR_API_KEY
The walking route ends at Montrose Harbor, 6,858 m from the pin in the lake. bicycle and hike returned the same end point. drive and light_truck rejected the point.
In the tests for this guide, drive mode accepted a point off the beach at 25th Street in Miami Beach that was about 430 m from the nearest drivable road, and rejected a point about 580 m away. Walking and cycling modes searched several kilometers. These distances are observed behavior, not documented limits, so do not build logic on exact values. Instead, compare the returned route with the input after every successful request, as shown in Detect moved waypoints.
What to do
- Check the coordinate order first. The message is the same for swapped latitude and longitude. If the point lies on land after swapping the values, the order is the likely cause.
- Ask the user to move the pin to a street, entrance, or parking lot. Highlight the waypoint you suspect, for example the one that is not on land in your map.
- Suggest another mode only when it makes sense for the trip. A pedestrian path may exist where no car can go, such as a park, a pier, or a car-free island.
- For waypoints from your own data, such as delivery addresses, geocode the street address instead of using a building centroid or a parcel point when the API rejects the point.
To find the failing waypoint in a multi-stop route, you can route each consecutive pair separately and see which pair fails. Each request costs credits, so do this only after an error, not for every route.
Handle waypoints with no connecting path
A waypoint can be next to a road and still be unreachable. The usual causes are an island without a bridge or ferry for the selected mode, a car-free area, or an avoid rule that removes the only connection. In these cases the API finds road positions for all waypoints but cannot connect them.
Ferry-only island with ferries avoided
Ocracoke, North Carolina, is reached by car on the NC 12 ferry from Hatteras. A drive from NC 12 in Hatteras (35.2193,-75.6905) to 495 Back Road, Ocracoke (35.1146,-75.9810) returns HTTP 200:
https://api.geoapify.com/v1/routing?waypoints=35.2193,-75.6905|35.1146,-75.9810&mode=drive&apiKey=YOUR_API_KEY
Reduced response:
{
"type": "FeatureCollection",
"features": [
{
"properties": {
"mode": "drive",
"distance": 40810,
"time": 5311.576,
"ferry": true
}
}
]
}
The steps include Take the Hatteras-Ocracoke Ferry/NC 12 Ferry. Add avoid=ferries and the same trip fails:
https://api.geoapify.com/v1/routing?waypoints=35.2193,-75.6905|35.1146,-75.9810&mode=drive&avoid=ferries&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "No path could be found for input"
}
The importance value changes the result:
avoid value |
Status | Result |
|---|---|---|
| none | 200 | 40,810 m route with "ferry": true |
ferries:0.9 |
200 | The same 40,810 m route with "ferry": true |
ferries |
400 | No path could be found for input |
ferries:1 |
400 | No path could be found for input |
The API currently treats ferries without an importance, or with importance 1, as a hard exclusion for this trip. With an importance below 1, it still uses the ferry when there is no other way. If your users want to avoid ferries but still reach islands, send a value below 1, for example ferries:0.9, and show a notice when the response has "ferry": true. See How to avoid tolls, highways, and ferries for the avoid syntax.
Car-free island
Motor vehicles are not allowed on Mackinac Island, Michigan. A drive from 410 South Huron Street, Mackinaw City (45.7775,-84.7275) to 7391 Market Street, Mackinac Island (45.8492,-84.6189):
https://api.geoapify.com/v1/routing?waypoints=45.7775,-84.7275|45.8492,-84.6189&mode=drive&apiKey=YOUR_API_KEY
{
"statusCode": 400,
"error": "Bad Request",
"message": "No path could be found for input"
}
Both points are next to streets, so the error is not No suitable edges. The same trip with mode=walk returns HTTP 200: a 13,466 m route, 2647.907 seconds, with "ferry": true for the passenger ferry. With mode=walk&avoid=ferries, it fails again with No path could be found for input.
What to do
- Remove or weaken
avoidrules and request the route again. Tell the user which rule was relaxed. - Offer another mode when the trip is possible that way, for example walking with a passenger ferry to a car-free island. For a delivery or service route, the alternative may be a drop-off point on the mainland instead.
- Ask the user to choose another destination or a reachable access point, such as a ferry terminal or a parking lot.
- Do not retry the same request. It returns the same error.
Detect moved waypoints
HTTP 200 means the API found a route, not that the route starts and ends where the user clicked. The walking request from the Chicago Loop to the pin in Lake Michigan succeeds:
https://api.geoapify.com/v1/routing?waypoints=41.8781,-87.6298|41.95,-87.55&mode=walk&apiKey=YOUR_API_KEY
Reduced response:
{
"type": "FeatureCollection",
"properties": {
"mode": "walk",
"waypoints": [
{ "lat": 41.8781, "lon": -87.6298 },
{ "lat": 41.95, "lon": -87.55 }
]
},
"features": [
{
"type": "Feature",
"properties": {
"mode": "walk",
"waypoints": [
{ "location": [-87.6298, 41.8781], "original_index": 0 },
{ "location": [-87.55, 41.95], "original_index": 1 }
],
"distance": 11217,
"time": 9904.11
},
"geometry": {
"type": "MultiLineString",
"coordinates": [
[
[-87.629771, 41.878101],
[-87.631077, 41.962993]
]
]
}
}
]
}
The line is reduced to its first and last coordinates; the full line has 644 points.
The response contains the input twice: properties.waypoints uses lat/lon, and features[].properties.waypoints[].location uses [longitude, latitude] with original_index. In the responses tested for this guide, both are the coordinates you sent, not the road position. The position the route actually uses is in the geometry:
- the first coordinate of each leg's line is where the route leaves that waypoint;
- the last coordinate of the last line is where the route arrives at the final waypoint.
Here the route ends at [-87.631077, 41.962993], at Montrose Harbor, 6,858 m from the requested [-87.55, 41.95]. The start moved only 2 m.
Check the distance in code
The following function compares each input waypoint with the route geometry and returns the waypoints that moved farther than a threshold. It works with the default GeoJSON format and the default stopover legs, where each waypoint pair has its own line:
// Great-circle distance in meters between two [lon, lat] points.
function distanceMeters([lon1, lat1], [lon2, lat2]) {
const r = 6371008.8;
const toRad = (d) => (d * Math.PI) / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
return 2 * r * Math.asin(Math.sqrt(a));
}
// inputs: [{ lat, lon }, ...] in the order sent to the API.
function findMovedWaypoints(routeGeoJson, inputs, maxMeters = 250) {
const lines = routeGeoJson.features[0].geometry.coordinates;
const routed = lines.map((line) => line[0]);
routed.push(lines[lines.length - 1].at(-1));
return inputs
.map((input, index) => ({
index,
input,
routed: routed[index],
meters: Math.round(distanceMeters([input.lon, input.lat], routed[index]))
}))
.filter((item) => item.meters > maxMeters);
}
For the Lake Michigan request, the function returns one item: index: 1, routed: [-87.631077, 41.962993], meters: 6858.
Choose the threshold for your use case. A door-to-door delivery app may warn above 100–250 m. A hiking or overview map can accept more. When a waypoint moves too far, show both points on the map and ask the user to confirm the routed point or move the pin.
With intermediate_waypoint_mode=pass_through, intermediate waypoints do not start new lines, so this check covers only the first and last waypoints. With format=json, the geometry is in results[].geometry, and each point is an object { "lon": …, "lat": … } instead of an array.
Check that the route is plausible
A route can also be valid but unusable. A bicycle route from 2412 Pacific Avenue, Virginia Beach, VA (36.8529,-75.9780) to 1101 Bayshore Road, Cape Charles, VA (37.2635,-76.0163), 45.8 km apart in a straight line, returns HTTP 200 with an 885,999 m route that crosses the James River on the Jamestown-Scotland ferry. The same points in drive mode return a 71,029 m route across the bay on US 13 (the Chesapeake Bay Bridge-Tunnel).
Compare the route distance with the straight-line distance between the waypoints. When the ratio is very high, for example above 3 for a local trip, show the distance and time before the user starts, or suggest another mode.
Choose the next action in code
Classify a failed response once and map it to one action. The following JavaScript function reads statusCode and message and returns what the application should do:
// Returns { action, reason } for a Routing API error response.
function classifyRoutingError(status, body) {
const message = body?.message ?? "";
if (status === 429 || status >= 500) {
return { action: "retry", reason: "temporary" };
}
if (status === 401) {
return { action: "fix-config", reason: "invalid-api-key" };
}
if (message.startsWith("Too long distance between locations")) {
return { action: "split-or-change-mode", reason: "distance-limit" };
}
if (message.startsWith("No suitable edges near location")) {
return { action: "move-pin", reason: "no-road-nearby" };
}
if (message.startsWith("No path could be found")) {
return { action: "relax-avoid-or-change-mode", reason: "no-connection" };
}
// Missing or malformed waypoints, unknown mode, and other input errors.
return { action: "fix-request", reason: "invalid-input" };
}
Match the beginning of the message, not the full text, because the distance message includes numbers. Swapped latitude and longitude also produce No suitable edges near location, so keep coordinates in named lat/lon fields in your code before relying on the move-pin action. Keep the final branch as a fallback for messages your code does not recognize.
Retry only temporary failures, with a short, growing delay and a small limit:
async function fetchRoute(url, maxAttempts = 3) {
for (let attempt = 1; ; attempt++) {
const response = await fetch(url);
const body = await response.json().catch(() => ({}));
if (response.ok) return { route: body };
const error = classifyRoutingError(response.status, body);
if (error.action !== "retry" || attempt === maxAttempts) {
return { error, message: body.message };
}
await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
}
}
After a successful response, run the moved-waypoint check before you show the route.
What to show the user
action |
User-facing result |
|---|---|
move-pin |
"We can't reach this point by car. Move the pin closer to a street." Highlight the waypoint and keep the map open. |
relax-avoid-or-change-mode |
"No route avoids ferries here." Offer to allow ferries, or another mode when it fits. |
split-or-change-mode |
"This trip is too long for a bicycle route. Add a stop or split it into stages." Include the limit from the message. |
fix-request |
A generic error for the user; log the request without the API key for the developer. |
fix-config |
A generic error for the user; alert the developer. |
retry |
A loading state during retries, then "Try again later" when all attempts fail. |
Keep the user's waypoints after an error so they can edit them instead of starting over. Do not include the API key in logs, error reports, or messages shown to the user.
Routing error handling with an AI coding agent
Copy this prompt into Codex, Claude Code, Cursor, or another coding agent to add routing error handling to your project.
Improve how my project handles failed and suspicious Geoapify Routing API responses.
Before you start
- Inspect the existing project first. Reuse its current framework, components, styling, HTTP client, and conventions. Make the smallest necessary changes.
- Find every place that calls https://api.geoapify.com/v1/routing and where the route result is shown.
- Ask only what the project does not answer: whether users place waypoints on a map or type addresses, which travel modes the UI offers, and which avoid options it sends.
- Keep the Geoapify API key in the existing environment configuration. Never hardcode, print, or log it.
Validate before sending
- waypoints is a pipe-separated list of latitude,longitude pairs (or lonlat:longitude,latitude). Map libraries and GeoJSON use [longitude, latitude]; convert explicitly.
- Require at least two waypoints, latitude -90..90 and longitude -180..180. Do not send address text; geocode it first.
Classify errors by statusCode and message
- For HTTP 400 waypoint and path failures, read { statusCode, error, message } and match the start of message:
- "Too long distance between locations": the straight-line distance between two consecutive waypoints exceeds the mode limit stated in the message. Suggest adding a stop, splitting the trip, or another mode.
- "No suitable edges near location": a waypoint is too far from a road or path for this mode, or lat/lon are swapped. Ask the user to move the pin.
- "No path could be found for input": the waypoints cannot be connected for this mode and avoid rules (for example avoid=ferries to a ferry-only island). Offer to relax the avoid rule or switch mode.
- Other HTTP 400 messages: show a generic input or path error and log the request without the key.
- Do not retry 400 responses. Retry 429 and 5xx at most 3 times with exponential backoff. Treat 401 as a configuration error.
Check successful routes
- features[].properties.waypoints[].location echoes the input, so compare each input waypoint with the route geometry instead: the first coordinate of each leg line, and the last coordinate of the last line.
- If a waypoint moved more than a configurable threshold (default 250 m), show both points and ask the user to confirm or move the pin.
- Show a notice when the route has "ferry": true while the user asked to avoid ferries with an importance below 1.
UI and tests
- Keep the user's waypoints after an error and highlight the problem waypoint when known.
- Add loading and error states without blocking the map.
- Add tests with mocked responses for each error message, a 429 retry, and a 200 response with a waypoint moved 6858 m.
Review the generated code against this guide. Check the coordinate order, that 400 responses are never retried, that the moved-waypoint check reads the geometry rather than waypoints[].location, and that the API key never appears in logs.