docs
/
AppEngine API

Logistics and delivery

Delivery jobs, pricing quotes, zones, drivers, live tracking, proof of delivery, customer orders with payment, and driver earnings and payouts.

LogisticsModule runs on-demand delivery: a customer asks for a pickup and dropoff, the job is priced, offered to drivers, driven stop by stop, and settled into the driver's wallet. Two surfaces: /logistics/delivery/* for operators (52 routes) and /client/logistics/* for drivers and the customers who order deliveries (54 routes).

Datatypes: delivery_config (pricing and assignment rules), delivery_zone (service areas), delivery_agent (a driver), delivery_job (one delivery). Payments are recorded as sf_transaction; driver earnings land as wallet_transaction credits.

Who may call

  • Operator routes carry @StaffOnly() and @Roles(RoleType.User) on the controller: a person signed in to the business. The site's app token and customers are refused.
  • Client routes need a JWT and nothing more at the controller — no @PublicRoute, no @Roles. The caller's own record id is used as their driver id and customer id, and the service does the rest:
    • Driver actions (accept, reject, start/arrive/complete pickup and dropoff, complete) require a delivery_agent whose status is approved or active; otherwise 403 with a message for pending/under_review, suspended or rejected.
    • Order routes (edit, cancel, pay, rate, tip, payment details) check the caller is the job's customer — by customer id or email — and answer 403 otherwise.
    • A driver reading a job they did not order gets it without pricing, payment and priceReconciliation.

The delivery lifecycle

delivery_job.status moves through:

pending → broadcasting → assigned → en_route_pickup → arrived_pickup → picked_up → en_route_dropoff → arrived_dropoff → delivered → completed

with cancelled and failed as exits. A job created with a pending, non-merchant payment starts at awaiting_payment and moves to pending once paid; a failed, cancelled or expired payment sets payment_failed.

  • Each stop has its own status (pending · arrived · completed). Completing a pickup stop moves the job to picked_up only when every pickup stop is done; the same for dropoffs and delivered. Proof ({ photos, signature } file references) is stored on the stop.
  • complete only runs from delivered (or a completed job whose earnings were never credited). It credits the driver's wallet with the driver pay and the tip as separate earning and tip entries referenced by job number, checks the ledger first so a retry never pays twice, frees the driver and sets them back online when they have no other active job.
  • The customer is notified when all pickups are done, when the job is delivered, and when it completes; the driver when it completes.

Pricing and addresses

POST/logistics/delivery/geocodeJWT
POST/logistics/delivery/validate-addressJWT
POST/logistics/delivery/quoteJWT
POST/logistics/delivery/route-distanceJWT
GET/logistics/delivery/configJWT

quote takes stops[] (type pickup/dropoff, location as coordinates or an address) and an optional configName, geocodes what it must and checks each stop is in a service area. config returns the named delivery_config, else the default; an organization with none gets a Default config created on first read.

Zones

GET/logistics/delivery/zonesJWT
GET/logistics/delivery/zones/lookup?lat=&lng=JWT
GET/logistics/delivery/zones/:zoneNameJWT

A delivery_zone is bounded by polygon, radius, zipcodes, cities, states or countries. zones lists active zones by default. zones/lookup returns the zone a point falls in. When a job is created, the first stop that falls in a zone sets the job's zone.

Drivers

GET/logistics/delivery/agentsJWT
GET/logistics/delivery/agents/onlineJWT
GET/logistics/delivery/agents/:agentIdJWT
PUT/logistics/delivery/agents/:agentId/locationJWT
PUT/logistics/delivery/agents/:agentId/availabilityJWT
PUT/logistics/delivery/agents/:agentId/approveJWT
PUT/logistics/delivery/agents/:agentId/suspendJWT
PUT/logistics/delivery/agents/:agentId/rejectJWT
GET/logistics/delivery/agents/:agentId/available-jobsJWT
PUT/logistics/delivery/agents/:agentId/recalculate-performanceJWT

A driver registers themselves through POST /client/logistics/register: the delivery_agent is created with the caller's id, status: pending, availability: offline, capacity.maxActiveJobs: 1, and the vehicle if one was sent. A second registration answers 409. Staff then approve, suspend or reject it, and the driver is emailed for each. agents/online returns approved/active drivers who are online, optionally within radius miles of lat/lng.

Jobs and assignment

POST/logistics/delivery/jobsJWT
GET/logistics/delivery/jobsJWT
GET/logistics/delivery/jobs/:jobIdJWT
PUT/logistics/delivery/jobs/:jobId/broadcastJWT
PUT/logistics/delivery/jobs/:jobId/offerJWT
PUT/logistics/delivery/jobs/:jobId/assignJWT
POST/logistics/delivery/jobs/:jobId/alert-agentsJWT
PUT/logistics/delivery/jobs/:jobId/acceptJWT
PUT/logistics/delivery/jobs/:jobId/rejectJWT

jobs with requireSystemQuote: true re-quotes on the server and refuses the job (400) if any stop is out of area; without pricing or driverPay, the price is calculated from the config and zone. broadcast offers the job to online drivers within the config's assignmentRules.broadcastRadius of the first pickup, each offer expiring after offerExpirySeconds. offer targets one driver, assign places the job directly, alert-agents notifies by agentIds, zone, online or a radius.

Accepting needs the job still pending or broadcasting (409 otherwise) and the driver under their maxActiveJobs; the other open offers expire.

Driving the job

PUT/logistics/delivery/jobs/:jobId/start-pickupJWT
PUT/logistics/delivery/jobs/:jobId/arrive-pickupJWT
PUT/logistics/delivery/jobs/:jobId/complete-pickupJWT
PUT/logistics/delivery/jobs/:jobId/start-dropoffJWT
PUT/logistics/delivery/jobs/:jobId/arrive-dropoffJWT
PUT/logistics/delivery/jobs/:jobId/complete-dropoffJWT
PUT/logistics/delivery/jobs/:jobId/completeJWT
PUT/logistics/delivery/jobs/:jobId/cancelJWT
PUT/logistics/delivery/jobs/:jobId/failJWT
PUT/logistics/delivery/jobs/:jobId/trackingJWT

Arrive and complete take stopIndex. Staff cancel takes cancelledBy, reason and optionally refund with refundAmount or refundPercent (full refund by default); a refund failure is returned alongside the cancelled job rather than undoing it. tracking stores the live lat/lng on the job.

Money, proof, issues and messages

PUT/logistics/delivery/jobs/:jobId/pricingJWT
GET/logistics/delivery/jobs/:jobId/adjustmentsJWT
POST/logistics/delivery/jobs/:jobId/adjustmentsJWT
PUT/logistics/delivery/jobs/:jobId/adjustments/:adjustmentId/removeJWT
GET/logistics/delivery/jobs/:jobId/payment-statusJWT
POST/logistics/delivery/jobs/:jobId/process-paymentJWT
GET/logistics/delivery/jobs/:jobId/imagesJWT
POST/logistics/delivery/jobs/:jobId/imagesJWT
POST/logistics/delivery/jobs/:jobId/issuesJWT
GET/logistics/delivery/jobs/:jobId/issuesJWT
PUT/logistics/delivery/jobs/:jobId/issues/:issueId/resolveJWT
PUT/logistics/delivery/jobs/:jobId/issues/:issueId/escalateJWT
POST/logistics/delivery/jobs/:jobId/messagesJWT
GET/logistics/delivery/jobs/:jobId/messagesJWT
GET/logistics/delivery/statsJWT

Adjustments are debit or credit entries — cleaning_fee, toll_fee, parking_fee, waiting_fee, damage_charge, cancellation_fee, bonus, rebate, discount, refund, penalty, other — with an optional driverPortion that flows into driver pay. Images are filed as proof, pickup, dropoff, issue, damage or other, and can be added after completion. Issues open as open and are resolved or escalated.

Driver app

GET/client/logistics/initJWT
POST/client/logistics/registerJWT
GET/client/logistics/meJWT
PUT/client/logistics/availabilityJWT
PUT/client/logistics/locationJWT
GET/client/logistics/jobsJWT
GET/client/logistics/jobs/availableJWT
GET/client/logistics/jobs/:jobIdJWT
PUT/client/logistics/jobs/:jobId/acceptJWT
PUT/client/logistics/jobs/:jobId/rejectJWT
PUT/client/logistics/jobs/:jobId/start-pickupJWT
PUT/client/logistics/jobs/:jobId/arrive-pickupJWT
PUT/client/logistics/jobs/:jobId/complete-pickupJWT
PUT/client/logistics/jobs/:jobId/start-dropoffJWT
PUT/client/logistics/jobs/:jobId/arrive-dropoffJWT
PUT/client/logistics/jobs/:jobId/complete-dropoffJWT
PUT/client/logistics/jobs/:jobId/completeJWT
PUT/client/logistics/jobs/:jobId/trackingJWT
POST/client/logistics/jobs/:jobId/issuesJWT
GET/client/logistics/jobs/:jobId/issuesJWT
POST/client/logistics/jobs/:jobId/imagesJWT
GET/client/logistics/jobs/:jobId/imagesJWT
POST/client/logistics/jobs/:jobId/messagesJWT
GET/client/logistics/jobs/:jobId/messagesJWT
PUT/client/logistics/jobs/:jobId/messages/readJWT
GET/client/logistics/jobs/:jobId/contactsJWT
POST/client/logistics/uploadJWT

init returns the driver's profile, their jobs, the wallet and — only when they are online and approved — the jobs they can take. jobs/available returns an empty list for an unapproved driver and never includes jobs the driver ordered themselves. jobs answers in driver mode for a registered driver (assigned jobs, active or delivered by default) and in customer mode otherwise or with ?role=customer. upload takes a multipart file and stores it under logistics/jobs/{jobId}/… (or logistics/uploads/… without a jobId).

Earnings and payouts

GET/client/logistics/payout-methodsJWT
POST/client/logistics/payout-methodsJWT
PUT/client/logistics/payout-methods/:methodIdJWT
DELETE/client/logistics/payout-methods/:methodIdJWT
PUT/client/logistics/payout-methods/:methodId/defaultJWT
GET/client/logistics/payoutsJWT
POST/client/logistics/payouts/requestJWT

Payout methods are bank, paypal, venmo, cashapp, debit_card or crypto, held on the driver's wallet; see Finance for wallets and payouts.

Ordering a delivery

POST/client/logistics/quoteJWT
POST/client/logistics/quote/validatedJWT
POST/client/logistics/jobsJWT
POST/client/logistics/ordersJWT
GET/client/logistics/ordersJWT
PUT/client/logistics/orders/:jobIdJWT
PUT/client/logistics/orders/:jobId/cancelJWT
GET/client/logistics/orders/:jobId/trackJWT
POST/client/logistics/orders/:jobId/payment-intentJWT
POST/client/logistics/orders/:jobId/complete-paymentJWT
POST/client/logistics/orders/:jobId/rateJWT
POST/client/logistics/orders/:jobId/tipJWT
POST/client/logistics/orders/:jobId/tip/completeJWT
GET/client/logistics/paymentsJWT
GET/client/logistics/payments/:jobIdJWT
GET/client/logistics/stripe/configJWT
POST/client/logistics/stripe/intentJWT
POST/client/logistics/stripe/verifyJWT
POST/client/logistics/paypal/createJWT
POST/client/logistics/paypal/captureJWT

orders prices the stops, creates the job for the caller and starts payment with paymentMethod stripe or paypal; complete-payment confirms it. A customer with an approved merchant account and enough available credit can pay with merchant_account instead, and the job goes straight to pending.

  • Edit only while the job is pending — once a driver is assigned it cannot change.
  • Cancel refunds a paid order by status: awaiting_payment or pending 100%, assigned 90%, en_route_pickup 50%. Any later status is refused. A failed refund does not block the cancellation.
  • Track is open to the customer or anyone whose phone matches a dropoff contact; it returns the status, stops, the driver's last reported position (tracking.currentLocation, with lastUpdate), the assigned driver, timeline and estimated/final price.
  • Rate (1–5) and tip only on delivered or completed jobs, by the customer who ordered. The rating is recorded against both the job and the driver.
  • Finance — wallets, payouts and payments.
  • Storefront — carrier shipping for store orders, separate from this module.