Route4Me and OnFleet Dispatch API: Connecting Re-Optimization to Driver Manifests

Route4Me and OnFleet dispatch API integration

Most last-mile platforms solve half the problem. Route4Me will re-sequence a run after a late arrival or a cancelled stop. OnFleet will push that revised manifest to the driver's phone. What neither platform does on its own is close the loop between them automatically, in time for drivers to sync before departure. In our experience working with regional parcel carriers, that gap is where plans fall apart.

Two Platforms, Two Mental Models

Route4Me thinks in terms of route optimization objects: a route_id, an ordered list of addresses, and time-window constraints that feed a VRP solver. OnFleet thinks in terms of tasks and workers: a worker has a queue, tasks have recipients, and the mobile app is the source of truth for what a driver sees on a given shift.

Neither model maps cleanly onto the other. Route4Me's sequence index (the sequence_no field inside each route destination) does not translate directly into an OnFleet task order. OnFleet sorts a worker's task queue by completionRequirement.order, which is a separate field you have to set explicitly. If you just push Route4Me stop data into OnFleet tasks without setting that field, drivers see tasks in creation order, not dispatch order. We've seen this cost 20 to 35 minutes per driver per shift in avoidable backtracking.

Understanding the mismatch is step one. Fixing it requires a thin integration layer that translates between the two data models.

Route4Me API: Reading Re-Optimized Sequences

After a re-optimization event, Route4Me exposes the revised stop sequence through its Route API. The endpoint you want is GET /api.v4/route.php?route_id={id}&output=Changes for delta-only reads, or the full route object via GET /api.v4/route.php?route_id={id} if you need to rebuild from scratch.

Each stop in the response is a destination object with a sequence_no starting at 1. That integer is your ordering key. Pull the full list of destinations, sort ascending by sequence_no, and you have the dispatch order Route4Me intends drivers to follow.

Practical note: Route4Me fires a webhook on optimization events if you register a callback via POST /api.v4/webhook.php. Event type route_optimized carries the route_id in the payload. Use that instead of polling. Polling Route4Me at sub-minute intervals under a 500-stop batch will get your key throttled.

OnFleet API: Writing Revised Task Order Back to Workers

On the OnFleet side, re-ordering an existing worker queue is a two-step operation.

  1. Look up active tasks for the worker. GET /api/v2/workers/{workerId}/tasks returns the worker's current queue. Map each task to its corresponding Route4Me stop by matching on address or a custom field you set at task creation (more on this below).
  2. Set the completion order. Use PUT /api/v2/tasks/{taskId} with the body {"completionRequirement": {"order": N}} where N is the Route4Me sequence_no for that stop.

The catch is that completionRequirement.order accepts the integer as a relative rank, not an absolute index. If you have 18 stops, you set values 1 through 18. Any task without an order value floats to an indeterminate position. Always update all tasks in the queue, not just the ones that moved.

For timing: OnFleet's default rate limit is 20 requests per second on the v2 API. An 18-stop route update means 18 PUT calls. At 20 RPS that completes in under 1 second. No issue there. Batches over 200 stops on a single worker account need pacing.

"Treating stop address as your join key between Route4Me and OnFleet works until a customer has two units in the same building. Use a stable external ID from day one."

The Join Key Problem

This is where most integration attempts break silently. When you create OnFleet tasks from Route4Me stops, you need a stable identifier that travels through both systems. Address strings are not reliable. They mismatch on unit number format, trailing commas, ZIP+4 vs ZIP5.

The correct approach is to write Route4Me's address_id (or your own internal stop ID) into OnFleet's metadata array at task creation time:

POST /api/v2/tasks
{
  "destination": { "address": { "unparsed": "123 Main St, Suite 4, Dallas TX 75201" } },
  "metadata": [
    { "name": "route4me_address_id", "type": "string", "value": "r4m_addr_88412" }
  ]
}

When a re-optimization fires and you pull the revised Route4Me sequence, query OnFleet tasks by metadata: GET /api/v2/tasks?metadata%5B0%5D%5Bname%5D=route4me_address_id&metadata%5B0%5D%5Bvalue%5D=r4m_addr_88412. You get the OnFleet task ID back. Then you PUT the updated order. Clean join, no address parsing.

Sequencing the Integration: Timing Matters

The window for pushing revised sequences to OnFleet is narrow. Most regional carriers have a hard cutoff: if a driver's app hasn't synced the updated manifest 15 minutes before departure, the driver goes with what they have. Miss that window and the re-optimization is wasted.

The architecture that holds up in practice looks like this:

  1. Route4Me fires route_optimized webhook to your integration service.
  2. Service reads the revised sequence from Route4Me (1 API call).
  3. Service queries OnFleet for affected worker tasks via metadata filter.
  4. Service PUTs updated completionRequirement.order for all tasks.
  5. OnFleet pushes the updated queue to the driver's mobile app via its own push notification.

Steps 2 through 5 complete in under 3 seconds for a typical 20-stop route. The entire loop, webhook receipt to driver push notification, runs in under 10 seconds. That is comfortably inside even a 15-minute pre-departure window. Honestly, the bottleneck is usually Route4Me's webhook delivery latency (2 to 8 seconds depending on server load), not the integration itself.

What This Doesn't Solve

A few things to be clear about. This integration pattern handles sequence updates for stops that already exist in OnFleet. It does not handle stop insertions or deletions mid-route. If Route4Me adds a new stop to a route that has already been pushed to OnFleet, you need to create a new task and set its order, which is a separate code path. Same for cancelled stops: OnFleet tasks in active state need to be explicitly failed or deleted, not just re-ordered around.

Also: this pattern assumes drivers are connected. If a driver loses signal and the OnFleet app is operating offline, order updates queue and sync on reconnect. That is fine for pre-departure changes. For mid-route re-sequences, you need to account for the possibility that a driver is already en route to stop 7 when you update the order of stops 8 through 12. OnFleet handles this gracefully, tasks already in inProgress state are not affected by order changes, but your integration logic needs to know not to reset a stop that is actively in progress.

Key Takeaways

  • Map Route4Me sequence_no to OnFleet completionRequirement.order explicitly. It is not automatic.
  • Use a metadata field as the join key between the two systems. Address strings will eventually mismatch.
  • Use Route4Me webhooks instead of polling. The route_optimized event type is your trigger.
  • Budget under 10 seconds for the full webhook-to-driver-push loop on a 20-stop route.
  • Handle insertions, deletions, and in-progress tasks as separate code paths from order updates.

The integration is not complicated. It is just specific. The Route4Me and OnFleet APIs are both well-documented, the endpoints you need are stable, and the rate limits are generous enough that a 200-stop dispatch batch is straightforward. Getting the join key right and understanding the task-order model are the only places where people consistently get stuck. Get those two things right and the rest falls into place.

Related Articles

Samsara and Motive routing integration
Integrations

Samsara and Motive Routing Integration

Post-cutoff route optimization
Operations

Post-Cutoff Route Optimization

Morning dispatch window for carriers
Operations

Morning Dispatch Window for Regional Carriers