One Endpoint, Multiple Systems: API Composition with Unmeshed
Most teams implement API composition inside route handlers: retries, branching, parallel calls, and response shaping. Here's how Unmeshed makes that orchestration explicit and exposes it as a backend endpoint, without building a dedicated service around it.
At some point in every backend's life, there's an endpoint that looks simple on the outside and is a disaster on the inside.
One route. One URL. But behind it: four service calls, a retry loop for the billing API that goes down every other Tuesday, conditional logic depending on account status, parallel fetches to reduce latency, and a response-shaping step that glues it all into something the frontend can actually use.
The handler started at 40 lines. Now it's 400. Two engineers have already said "we should really clean this up" and moved on to something more urgent. And every time product changes a rule, someone has to spelunk through the handler, figure out where the branching is, hope the tests catch any regressions, and push it out.
This is not a code quality problem. It is an architectural one. The endpoint is doing orchestration work, but it is living in a place that was never designed to hold orchestration.
Unmeshed is built specifically for this situation. The workflow becomes the execution layer behind the endpoint: explicit, visible, and designed to coordinate across systems.
The endpoint is not the hard part
The route itself is trivial. Five lines. Register the path, parse the input, return the response.
The hard part is what the route has to do:
- validate the incoming request against a contract
- call internal services that may or may not be available
- run independent lookups in parallel because sequential calls are too slow
- apply business rules that have changed three times this year
- retry dependencies that fail transiently
- fall back gracefully when one system is unavailable
- shape four different service responses into one coherent payload
Once an endpoint is doing all of that, HTTP is almost incidental. The real work is coordination.
That coordination is exactly what a workflow engine is designed to carry. The problem is that most teams never move it there. They just keep growing the handler until it becomes the most complex, least understood part of the backend.
What this actually looks like
Here is a real-world example of the kind of endpoint that gets into trouble fast.
A frontend dashboard, a partner integration, or an internal tool needs one call that returns a unified customer view. Something like:
GET /customer/360?customerId=cus_123The caller does not want to make five requests and stitch the answer together. It wants one response: profile, subscription state, recent orders, open support tickets.
Behind that single request, the backend has to coordinate validation, parallel service calls, retry logic, fallback behavior, business rules, and response shaping.
Left: retries, fallbacks, and parallel fetches buried inside a single route handler. Right: the same steps as an explicit Unmeshed workflow, named, ordered, and visible.
None of that is especially exotic. But all of it living inside a route handler is the slow-motion version of a bad day.
The retry logic for billing sits next to the response shaping. The fallback for the support system is buried three levels deep in an if-block. The parallel fetch is a Promise.all that someone added six months ago, and nobody is fully sure what happens if one of the promises rejects.
When something goes wrong in production, and something always goes wrong, the debugging process is: check logs across four services, hope the correlation IDs were propagated, piece together a timeline, and pray the failure was in a path that actually has logging.
What changes when the workflow is the execution layer
With Unmeshed, the endpoint becomes the entry point. The workflow owns the coordination.
The caller still makes one request to one URL. But the steps, the branching, the retries, the parallel execution: all of that lives inside an explicit workflow definition instead of being scattered across the handler and its dependencies.
Your services still own their domains. The profile service still owns profile data. Billing still owns subscription state. The support platform still owns tickets. What moves out of the handler is the logic that coordinates between them.
That separation matters more than it might sound. It means:
The coordination logic is visible. Instead of reconstructing what the handler does by reading code, you can see the execution graph directly. Which steps ran. Which step failed. Which branch was taken. What retried, and whether it succeeded.
Failures are easier to reason about. When billing is unavailable, the retry policy is defined in the workflow, not implied by a try-catch somewhere in the handler. When the support system is down, the fallback behavior is explicit, not dependent on whether a developer remembered to handle that case.
Changes are scoped. When product changes the flagging rule for accounts with failed payments, that change lives in the workflow step that applies business rules, not threaded through a handler that also happens to be responsible for parallel fetches and response shaping.
The response contract stays stable. The caller gets one clean shape regardless of how many systems participate behind the scenes.
{
"customerId": "cus_123",
"profile": {
"name": "Ava Johnson",
"email": "[email protected]"
},
"subscription": {
"plan": "growth",
"status": "active"
},
"recentOrders": 4,
"openTickets": 1,
"flags": ["payment_review"]
}The frontend does not need to understand your internal architecture. It needs a stable shape. The workflow guarantees that shape regardless of which dependencies had a bad day.
Calling it looks like any other API
This is not a special invocation model. From the caller's perspective, it is a normal endpoint.
curl -X GET "https://your-unmeshed-endpoint/customer/360?customerId=cus_123"Or as a POST with a structured body:
{
"customerId": "cus_123",
"includeOrders": true,
"includeSupport": true
}The caller sees one endpoint, one stable contract, one response. The fact that the execution layer is a workflow instead of a handler is an implementation detail, but it's the implementation detail that makes the whole thing maintainable.
Not every endpoint needs to block
One more thing worth making explicit: the workflow-as-endpoint pattern works for both synchronous and asynchronous flows.
Some endpoints need to return a completed response immediately. The customer 360 example is synchronous: the caller is waiting. Eligibility checks, pricing calculations, BFF aggregation endpoints, all of these are cases where the caller needs the answer before it can do anything else.
Other endpoints just need to confirm that a process has started. Onboarding flows, document processing, approval workflows, signup qualification: these are cases where the backend kicks off work that continues after the response. The caller gets a confirmation while the workflow continues in the background.
In both cases, Unmeshed owns the coordination. The endpoint is the entry point. The difference is just whether the caller waits for the result or not.
Which endpoints actually benefit from this
Not every endpoint needs orchestration. If a route is a thin pass-through to one service, adding a workflow layer is unnecessary overhead.
But for endpoints that are already doing real coordination work, the workflow model is almost always cleaner than the alternative. Good candidates:
- BFF endpoints that aggregate data from multiple services before returning to a client
- Partner-facing APIs that need a stable response contract regardless of backend changes
- Internal APIs that coordinate business rules across teams or systems
- Any endpoint where retries, fallbacks, and branching logic have started accumulating in the handler
If an endpoint has ever made you think "I'm not sure exactly what this does anymore," it is probably a workflow that has not been given a proper place to live.
The real question
Most teams do not make an active decision to grow orchestration logic inside route handlers. It just happens, one added dependency at a time, one edge case at a time, until the endpoint is doing work that was never supposed to be its job.
The question is not whether to have orchestration. You already have it. The question is whether it stays buried in handlers and helper modules, or lives somewhere designed to coordinate it explicitly.
For endpoints doing real orchestration work, Unmeshed gives that logic a place designed to hold it: visible, manageable, and built for the kind of multi-system coordination that makes route handlers hard to live with over time.
If your backend endpoints are coordinating multiple systems, applying branching rules, and triggering follow-up work, Unmeshed gives that logic a cleaner place to run.
Bring an endpoint or backend flow that has gotten harder to maintain than it should be.


