Logistics doesn't integrate with parcel carriers (UPS, FedEx, USPS, DHL) — that's the Storefront's shipping module. What this module does manage is your own carrier network: the drivers, riders, and couriers (collectively "agents") who pick up and drop off jobs. They register through the driver app, take a job, and run it to completion.
For parcel carrier integration in e-commerce orders, see Storefront shipping.
Agent endpoints
/logistics/delivery/agentsJWT/logistics/delivery/agents/onlineJWT/logistics/delivery/agents/:agentIdJWT/logistics/delivery/agents/:agentId/locationJWT/logistics/delivery/agents/:agentId/availabilityJWT/logistics/delivery/agents/:agentId/approveJWT/logistics/delivery/agents/:agentId/suspendJWT/logistics/delivery/agents/:agentId/rejectJWT/logistics/delivery/agents/:agentId/available-jobsJWTDriver self-service
/client/logistics/initJWT/client/logistics/registerJWT/client/logistics/meJWT/client/logistics/availabilityJWT/client/logistics/locationJWTDriver registration
A new driver registers via /client/logistics/register. The record is created in pending status; an admin reviews and approves before the driver can take jobs.
await fetch('/client/logistics/register', {
method: 'POST',
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
firstName: 'Sam',
lastName: 'Park',
email: '[email protected]',
phone: '+1 555 0123',
vehicleType: 'car', // 'bike' | 'car' | 'van' | 'truck'
licenseNumber: '...',
licensePlate: '...',
homeZone: 'downtown',
documents: { driversLicense: '...', insurance: '...' },
}),
});
Staff approve through PUT /logistics/delivery/agents/:agentId/approve. Suspending and rejecting follow the same shape with appropriate audit reason.
Online status and location
A driver's app calls these on a heartbeat to keep dispatch informed:
// Toggle online / available
await fetch('/client/logistics/availability', {
method: 'PUT',
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
online: true,
available: true, // false = on a job
}),
});
// Push location every 10–30 seconds while online
await fetch('/client/logistics/location', {
method: 'PUT',
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
lat: 40.7128,
lng: -74.006,
heading: 90,
speed: 12.5,
}),
});
Location updates power both the tracking endpoint and the dispatcher's nearest-agent calculation.
Zones
Zones are named geographic areas. Each zone has a polygon (or simpler bounding shape) and a config for surge multipliers, agent caps, and accepted vehicle types.
/logistics/delivery/zonesJWT/logistics/delivery/zones/:zoneNameJWT/logistics/delivery/zones/lookupJWT// Find the zone for a coordinate
const { zone } = await fetch(
'/logistics/delivery/zones/lookup?lat=40.7128&lng=-74.006',
{ headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}` } }
).then(r => r.json());
Zones are seeded through the data API (POST /repository/create/delivery_zone/create) — there isn't a dedicated zone-mgmt controller. Drivers carry a homeZone and can be filtered by it.
Dispatch — broadcast, offer, assign
Three ways to put a job in front of an agent:
Broadcast
Open the job to any qualifying online agent. First-come, first-served.
await fetch(`/logistics/delivery/jobs/${jobId}/broadcast`, {
method: 'PUT',
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}` },
});
The job appears in GET /client/logistics/jobs/available for every eligible agent (vehicle type matches, in zone, online, available). The first to accept wins.
Offer
Push the job to a specific agent first. They get a window to accept or reject; if rejected or the timer expires, fall back to broadcast.
await fetch(`/logistics/delivery/jobs/${jobId}/offer`, {
method: 'PUT',
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ agentId: 'agent-xyz' }),
});
Direct assign
Skip the agent's accept/reject — staff assigns the job and the agent is notified to start. Use for VIP agents or back-office workflows.
await fetch(`/logistics/delivery/jobs/${jobId}/assign`, {
method: 'PUT',
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ agentId: 'agent-xyz' }),
});
Targeted alerts
Push a notification (in-app + push) to a curated set of agents — by id, by zone, by online state, or within a radius:
/logistics/delivery/jobs/:jobId/alert-agentsJWTawait fetch(`/logistics/delivery/jobs/${jobId}/alert-agents`, {
method: 'POST',
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
online: true,
radius: { lat: 40.7128, lng: -74.006, miles: 3 },
}),
});
This is most useful when a broadcast hasn't been picked up yet — manually nudge nearby drivers without changing the job's dispatch state.
Performance stats
Each agent record carries denormalised stats — completed jobs, average rating, on-time rate, total earnings. Recompute manually if you suspect drift:
/logistics/delivery/agents/:agentId/recalculate-performanceJWTThis walks the agent's full job history and rewrites the stats block from source.
Drivers and customers share the same JWT scheme — the role is determined by which collection the principal record lives in (delivery_agent vs regular customer). The driver app should confirm GET /client/logistics/me returns 200 before showing driver-only screens.