How to Fake a Vendor API the Right Way: Serving Mapbox Directions from Valhalla
Compatibility layers are easy to underestimate. On the surface, they look like translation work. In reality, they are product work.
This gateway exposes a GET /directions/v5/mapbox/:profile/:coordinates.json endpoint in src/routes/mapboxRoutes.js, but the backend engine is Valhalla, not Mapbox. That means the service is not just converting one request format into another. It is trying to preserve a developer experience that clients already understand while running on a completely different routing stack.
That is a much harder and more interesting problem than “proxy this request somewhere else.”
Why this pattern matters
A compatibility layer is often the fastest way to reduce adoption friction. If developers already know a popular API contract, you can meet them where they are instead of forcing an immediate rewrite. That is especially valuable when you are replacing a vendor dependency, introducing a self-hosted alternative, or building a product that wants to support existing SDK assumptions.
In this repo, the Mapbox adapter sits on top of the same unifiedProxy() flow as the native Valhalla endpoints. That is the right architectural choice. It means routing, region selection, caching, and cross-region fallback are all shared. Only the edge translation differs.
What the adapter really has to do
The interesting logic lives in src/utils/mapboxDirections.js.
It maps:
- Mapbox profiles like
drivingandcyclinginto Valhalla costing modes - Mapbox query parameters into Valhalla request body fields
- Valhalla maneuvers into Mapbox maneuver types and modifiers
- Valhalla geometry and leg structures into a Mapbox-like response shape
- approximated annotations, voice instructions, and banner instructions into something clients can consume
The word “approximated” matters. This is what separates good compatibility writing from fake precision. Some parts of the destination API have no exact one-to-one representation in the source system. The code handles that pragmatically rather than pretending the mismatch does not exist.
For example, annotation arrays are inferred from leg summaries rather than coming from a native equivalent. That is a very defensible product decision when you need a useful shape quickly, but it also deserves to be documented honestly.
Where these projects often go wrong
The most common mistake in vendor compatibility work is optimizing for green integration tests instead of real client behavior. A response can have the right field names and still be subtly wrong in the places real apps care about: maneuver semantics, geometry precision, waypoints, summary values, or edge-case errors.
This code avoids some of that by doing more than mechanical mapping. transformStep() corrects depart and arrive semantics, tries to infer maneuver modifiers from instruction text when upstream typing is not enough, and supports multiple geometry output styles. That is the kind of detail work that makes adapters feel credible.
The tradeoff no one likes to admit
Once you publish a compatibility layer, you inherit part of another product’s surface area.
That means you now have to care about:
- which features are exact matches and which are best-effort
- how to version your behavior when the upstream model changes
- what clients will assume because the URL looks familiar
- how much drift you can tolerate before the compatibility promise becomes misleading
This is not a reason to avoid adapters. It is a reason to treat them as long-lived product interfaces instead of short-lived migration glue.
What I would do next
If I were pushing this further, I would add explicit compatibility tests using real Mapbox-shaped client expectations, not just internal unit tests. I would also document where the adapter is intentionally approximate: annotations, certain maneuver semantics, and any unsupported edge cases.
I would probably publish a compatibility matrix too. Developers love clear contracts. “Supported,” “best effort,” and “not implemented” is much more trustworthy than a generic claim of API compatibility.
The bigger lesson
Good adapters do not hide all differences. They absorb the painful ones and document the meaningful ones.
That is what makes this Mapbox-on-Valhalla design interesting. It is not trying to imitate a vendor for the sake of imitation. It is using a familiar contract as an onboarding strategy while still keeping the backend architecture under our control. That is not just API design. That is distribution strategy expressed in code.