NETWORK · LOGISTICS PORTAL

The cockpit your forwarders work in. Branded for your operations.

Your forwarder updates you by email. The dispatch team posts milestones into a shared spreadsheet someone keeps forgetting to refresh. Customs clearance dates arrive as PDF screenshots. Rate cards live in a SharePoint folder. When the container slips, you find out from the importer asking why. Your “shipment record” is a fiction reconstructed from three inboxes and a WhatsApp.

The Logistics Portal in TradeOS is a branded cockpit your forwarders, brokers and carriers work in directly — same database, scoped to what they need to see. Structured Inbox replaces ad-hoc email. Real milestones land on real state machines. Customs, dispatch, invoicing and conversations all on one record. Multi-tenant identity: one login, many operator workspaces, each one isolated. Free for the provider. Not a TMS replacement.

Book a demoSee pricing

Portal sections

7 · inbox, shipments, dispatch, invoices, and 3 more

Logistics modes

Ocean, air, truck, rail, express, and 3 more

Multi-tenant identity

One login, many operator workspaces

Cost to forwarder

$0 · operator pays for TradeOS

HPendrew EnergyLogistics workspace · Oceanway Forwarding, Klang
Switch operator ▾
HomeInbox · 4ShipmentsDispatchRatesInvoices · 3MessagesSettings
Wednesday morning
Good morning, Rachel. 4 things need you today · 2 booking requests pending, 1 RFQ deadline at 14:00, 1 exception declared overnight.
GLOBAL SITUATION
6 active shipments
Open shipments →
Needs response3 items
BOOKING
BKG-2026-4419 · Klang → Hamburg · 1×40HCReady 28 May · ETA req 14 Jun · sent 18h ago
AcceptCounter
BOOKING
BKG-2026-4424 · Shanghai → Rotterdam · 2×40HCReady 02 Jun · ETA req 20 Jun · sent 6h ago
AcceptCounter
RFQ
RFQ-2026-0188 · Port Klang → North Europe · Ocean FCLDeadline 14:00 today · ready 10 Jun
Submit quote
At risk1 exception · critical
CRITICAL
SHP-2026-018 · Port of Klang gate-in delayeddeclared 03:17 by ops · operator notified · mitigation: book later vessel
Mitigating
Today5 commitments
10:00Pickup · SHP-2026-026 · Long Beach
14:00RFQ-2026-0188 deadline · Klang → N. Europe
18:00VGM cutoff · SHP-2026-024 · Ningbo
19:30Departure · SHP-2026-022 · Shanghai
22:15Arrival · SHP-2026-016 · Hamburg
Money$184,920 outstanding
Drafts2
Submitted5
Under review3
Approved8
Paid · 30d14
Overdue2
Disputed1

The real Home dashboard — greeting strip, world map widget with active shipments at their positions, two-column action queue. Single backend call (GET /portal/logistics/home) returns everything. Operator brand chrome (logo, color, workspace label) renders from the operator's tenant settings.

THE MILESTONE THAT NEVER UPDATES

What “shipment visibility” really looks like today.

The forwarder is competent. The broker is fast. The carrier's tracking page exists. None of that helps when the system of record for a single shipment is split across three inboxes, two spreadsheets, a SharePoint folder, and a WhatsApp group nobody's in the same time zone for.

01 · MILESTONES LIVE IN A SHARED SPREADSHEET

“What’s the ETA?” gets the answer from a tab someone forgot to refresh.

Your forwarder updates a Google Sheet on a different cadence than the importer who's chasing you. The ops team has two sources of truth — the spreadsheet and the carrier's tracking page — and they disagree by three days. By the time you pull the answer, the container has cleared customs without anyone noticing.

Forwarder spreadsheetOperator record
3-day drift·same container

02 · CUSTOMS UPDATES LIVE IN EMAIL THREADS

The broker sends “we filed entry on Tuesday” at 11pm. You read it Thursday.

The customs broker is fast but their channel is whatever inbox they had open at the time. The release notification arrives as a PDF attachment. You forward it to accounting, who can't find the entry number because it's inside the screenshot. The shipment goes out three days late because nobody knew it was cleared.

Broker email · PDFCustoms record
3 days late·entry buried in screenshot

03 · ONE FORWARDER, FIFTEEN PORTALS

Your forwarder ignores the portal you sent them because they have fifteen others.

You stand up “your” portal and ask your forwarder to log in to update milestones. They have one for every client. They check none of them consistently. Your data goes stale the moment they close the browser tab. The portal becomes a system the forwarder treats as a chore — until it doesn't.

Yet another portalForwarder ignores it
1 login · many ops·used because it's shared

7 SECTIONS · ONE PORTAL

Every surface the forwarder works on, in one navigation.

Real LOGISTICS_NAV.flatLinks from client/src/lib/navConfig.ts. Mobile bottom-nav uses the first four: Home · Inbox · Shipments · Dispatch.

01

Home

Greeting strip + world map with active shipments + 2-column action queue (Needs Response, At Risk, Today, Money). Single backend call returns everything.

02

Inbox

The structured-response queue. 3 sub-tabs (bookings, RFQs, disputes), each with Needs Action / Awaiting Response / Recently Decided sections. 48h auto-promote on stale counter-quotes.

03

Shipments

5 tabs — Tracking, Containers, Costs, Customs, History. One backend call (/shipments/overview/all) returns everything; tabs are projections.

04

Dispatch

11-state machine for ground transport. assigned → accepted_by_driver → en_route_to_pickup → arrived → loaded → departed → en_route_to_delivery → arrived → unloaded → completed (+ failed branch). POD-gated completion.

05

Rates

Rate sheets with valid-from / valid-to / version / superseded-by. Per-line surcharges, volume tiers, minimum charge. Bulk import 1-500 lines atomic.

06

Invoices

10-state machine. 3 types: per-shipment, monthly-consolidated, ad-hoc. Dispute branch with resolution tracking.

07

Messages

Threaded messaging scoped per operator connection. Attached to entity (booking, RFQ, shipment, invoice). Notifications via the same fabric as the other portals.

THE INBOX · 1,252 LINES OF UI

The forwarder’s inbox lives in your record — not in three email threads.

Three workflow types share one queue because from the forwarder’s perspective they’re all “structured response, time-pressured, I’m the bottleneck”: booking requests, RFQs, and invoice disputes. The Inbox surface puts them in one queue with inline action forms so the common path needs zero navigation.

All 12Bookings 4Quotes (RFQs) 5Disputes 3
Needs action · 4
BOOKING
BKG-2026-4419
Klang → Hamburg · Ocean FCL · 1×40HC · ready 28 May
BOOKINGSTALE · FOLLOW UP
BKG-2026-4401 · counter-quoted
Ningbo → Hamburg · Counter sent 51h ago · awaiting operator
RFQ
RFQ-2026-0188
Port Klang → North Europe · Ocean FCL · deadline 14:00 today (5h)
DISPUTE
INV-2026-1142 · disputed
Demurrage charge contested · $1,840 · operator note “vessel arrived 2 days early”
Awaiting response · 5

5 quotes submitted · awaiting operator decision · oldest 18h

Recently decided · 3

2 quotes won · 1 quote lost · expand to see

SHIPMENTS · 5 TABS · ONE BACKEND CALL

Five projections of the same shipment data.

The forwarder’s primary working surface. GET /portal/logistics/shipments/overview/all returns everything once; the page keeps it in memory and projects per tab. No fan-out from the browser, no per-tab re-fetch.

01

Tracking

World map + list with status, route, last milestone, open exceptions. Click a row → inline expansion showing the full milestone timeline + open exceptions for that shipment.

02

Container planning

Every container on every shipment, with type, utilisation (CBM / weight vs container max), seal #, loading date. The “what’s loaded where” view.

03

Costs & freight

Per-shipment revenue from invoices, paid amount, outstanding. Aggregate totals at top. Drill into invoice detail from any row.

04

Customs & compliance

Per-shipment customs filings: jurisdiction (ISO 3166), declaration type (import/export/transit), status (7-state machine), declaration number, HS codes filed. Aggregate cleared/pending/held at top.

05

History

Chronological merge of events + milestones + exceptions across all of this forwarder’s shipments. Time-ordered feed. The audit log surface.

STATE MACHINES · ENFORCED IN DB

Six lifecycles the forwarder works through — without ever leaving your record.

Every workflow has an explicit state machine — Postgres enum types or CHECK constraints. Service-layer transition rules validate which states can move to which. No magic strings, no UI-side state.

BOOKING · 6 STATES

pending_acceptancecounter_quotedaccepted

Terminal alts: declined · cancelled · expired. Accepting creates the linked shipment.

RFQ · 7 STATES

sentquotedwon

Branches: lost · declined_to_quote · expired · withdrawn. Backend enforces one-pending-quote-per-RFQ.

DISPATCH · 11 STATES

assignedaccepted_by_driveren_route_to_pickuparrived_at_pickuploadeddeparteden_route_to_deliveryarrived_at_deliveryunloadedcompleted

Any active state can transition to failed with a reason. Completion requires POD captured first (gated by tooltip in UI).

INVOICE · 10 STATES

draftsubmittedunder_reviewapprovedscheduledpaid_partialpaid

Branches: overdue · disputed · written_off. Three invoice types: per_shipment · monthly_consolidated · ad_hoc.

CUSTOMS FILING · 7 STATES

draftsubmittedacceptedcleared

Branches: held (pending docs / payment / inspection) · queried (customs asked a question) · rejected (terminal). Per-jurisdiction × declaration_type.

EXCEPTIONS · 3+3 ENUM

info/warning/critical

Severity × status. Status moves open → mitigating → resolved. Distinct from milestones — declared, mitigation tracked, resolution audited.

8 LOGISTICS MODES · PER-MODE MILESTONE TEMPLATES

Eight logistics modes — because real shipments don’t fit one.

The logistics_mode enum has eight values, each with its own per-mode milestone template (validated service-side, not via DB enum so new milestones can be added without ALTER TYPE).

Ocean FCL
Fields: container number, vessel, voyage, BL number, VGM, ports of loading & discharge.
Milestones: booked → VGM filed → gate-in → loaded → departed → transshipped → arrived → discharged → customs → delivered.
Ocean LCL
Fields: CFS, master & house BL, consolidation reference, packing list.
Milestones: booked → CFS received → consolidated → loaded → departed → arrived → deconsolidated → customs → delivered.
Air
Fields: AWB, flight, ULDs, airports, dangerous goods declaration.
Milestones: booked → manifest → handover → uplifted → in-flight → landed → received → customs → delivered.
Truck FTL
Fields: plate, driver, trailer, container, single-stop pickup + delivery.
Milestones: Full 11-state dispatch machine (above).
Truck LTL
Fields: per-stop sequencing, mixed shipper consolidation, signed POD per stop.
Milestones: dispatch machine with multi-stop pickup + delivery loops.
Drayage
Fields: chassis, port appointment, customs hold release, container drop / live unload flag.
Milestones: dispatch machine + port appointment workflow.
Rail
Fields: wagon, train, terminals, gauge change milestones.
Milestones: dispatched → loaded → departed → border → gauge change → arrived → unloaded → handover.
Express / parcel
Fields: tracking number, carrier, last-mile attempts, signed POD.
Milestones: picked up → in transit → out for delivery → delivered (or returned).

ONE FORWARDER · MANY OPERATORS

One forwarder login. Many operator workspaces. Each one isolated.

A forwarder serving Pendrew Energy, AcmeCorp, BioMed Global, and seven others gets one users row, one login, ten operator workspaces via the external_memberships table (migration 121). Switch with one click. Each operator’s bookings, rate sheets, milestones, invoices, and conversations are isolated by connection_id — there’s no API path through which data fans out across operators.

Your carrier API credentials live at the external_organization level (migration 289) — set up once, encrypted via the KMS plugin, reused across every operator workspace you serve.

Pendrew Energy
EU subsidiary · 4 active shipments · 2 awaiting cutoff
Active workspace
AcmeCorp Trading
Singapore HQ · 7 active · 1 customs hold
Switch →
BioMed Global
3 active · invoice approved
Switch →
+ 7 more operator workspaces
Total: 10 connected operators
View all
Each isolated · operators don’t see each other’s data

CONFIDENTIALITY · ARCHITECTURAL

Cross-operator confidentiality is enforced in the platform, not policy.

Architecture, not handshake. Real redactFor(entity, row, viewerRole) in server/src/modules/portal-shell/redaction.ts — same pattern as the supplier and client portals.

PER-CONNECTION SCOPE

Every query carries a connection_id from the JWT

There is no path through the logistics-portal API that fans out across operators. Cross-operator access returns 404 (not 403) so the existence of the resource is hidden — verified in the projector test suite (portal-logistics/__tests__/projector.test.ts).

FIELD-LEVEL REDACTION

Logistics never sees commercial values

Buy price, sell price, margin, cost basis — stripped before the row leaves the database. Real redactShipmentForClient and redactFor('shipment', raw, 'logistics') functions enforce this. The exception is the BOL: legally-required consignee data appears on the BOL document, governed by Incoterm-driven visibility (next section).

RATE PRIVACY

Your rates with one operator are not visible to others

Rate sheets are scoped per logistics_connection_id. The operator-side rate marketplace exists for comparing across providers (Solo+ feature), not the reverse — forwarders cannot enumerate “who else gets a better rate than I do.” RFQ outcomes (won / lost) are surfaced to the forwarder themselves but never aggregated across forwarders.

INCOTERMS · VISIBILITY CONFIG

What the forwarder sees depends on which party is the legal shipper.

The connection’s visibility_config JSON (migration 122 column, schema docs in 291) sets per-incoterm visibility for origin and destination — country / port / city / factory_name / factory_address / full_address. Plus per-incoterm overrides and label overrides.

DDP · CIF · CFR

Operator = legal shipper of record

Factory identity is abstracted on shipping documents. The forwarder sees the operator’s address as origin; the operator’s tenant settings drive the BOL shipper field. The factory name is never rendered to the forwarder unless explicitly overridden.

OriginOperator address ▸ Port Klang, MY
DestinationConsignee address ▸ Hamburg, DE
BOL shipperOperator legal entity

FOB · EXW · FCA

Factory = legal shipper of record

The factory is the legal shipper on the BOL because it has to be there legally. Factory name and pickup address are visible to the forwarder; the visibility_config can still constrain what the operator’s downstream client sees (the client-portal redaction applies separately).

OriginFactory ▸ Crescent Mfg, Klang
DestinationPer Incoterm
BOL shipperFactory legal entity
DRIVER · Oceanway
SHP-2026-026 · DRAYAGE
Long Beach Port → AcmeCorp DC, Compton
CURRENT STATE
arrived_at_pickup
Captured 14:08 · GPS 33.7701, −118.2186 · 1 photo
NEXT
loaded → departed
Confirm seal #, tap to advance
Confirm loaded
PWA · offline-tolerant · syncs when reconnected

DRIVER · PWA

Same portal, scoped to “your dispatches only.”

Drivers install the Logistics Portal to their home screen — manifest.webmanifest + service worker — sign in once, and get the dispatch queue scoped to assignments where they’re the assigned driver. No rates, no invoices, no other shipments. Milestones capture GPS lat/lng and photo uploads; POD requires signature + signer name + signer title before completed transition is allowed.

Six milestone source values are recorded per event: user (ops manual entry), driver (mobile capture), carrier_api, ocr, scheduled_auto_advance, operator_override. The milestone table is append-only — UPDATE is refused by trigger, corrections insert new rows with supersedes_milestone_id pointing at the original.

Geofence-driven auto-transitions, OCR pipeline for BOL upload, and native iOS / Android apps are on the roadmap — the milestone source enum already reserves their slots.

TIER GATING · 15 FEATURE KEYS

Free for the forwarder. The operator’s tier decides which capabilities show up.

The real tier_features registry (migration 291) defines fifteen logistics-portal feature gates. Logistics providers never see a paywall; the connecting operator’s tier decides what’s available in your workspace for that operator. [Roadmap] tags mark features whose tier_feature key exists but whose implementation is post-launch.

Feature
Starter
Solo
Business
Enterprise
Bookings + shipments + manual milestones
Driver PWA + mobile milestone capture + POD
Document upload + visibility-tag sharing
Rate sheets + RFQ flow (6-state booking · 7-state RFQ)
Carrier API integration (project44 live; more in development)
Multi-carrier marketplace (compare quotes)
Automated customs docs (per-shipment template fill)
Direct US ABI/ACE filing (licensed brokers)
AI consolidation suggestionsRoadmap
Cross-tenant performance insightsRoadmap
Network reputation aggregationRoadmap
Multi-operator consolidationRoadmap
Custom carrier APIs · TMS direct integration · SAML SSO

Roadmap = tier_features key registered in migration 291 (see db/migrations/291_logistics_tier_features_and_visibility_overrides.sql); engine / aggregation / UI implementation lands post-launch per Logistics Spec §19, §14, §25.

ON THE ROADMAP

What’s coming to the Logistics Portal.

v1 ships seven sections, six state machines, append-only milestone log, multi-tenant identity, field-level redaction, Incoterm-driven visibility, and tier gating across fifteen feature keys. Several capabilities the marketing team would love to claim are flagged honestly as roadmap.

01

AI consolidation suggestions

Migration 290 ships the consolidation_groups table schema. The suggestion engine that populates this table is a post-launch background job; the v1 UI surfaces nothing yet. Tier feature key logistics.ai_consolidation_suggestions registered for Business+.

02

Cross-tenant performance insights + network reputation

Tier feature keys logistics.cross_tenant_insights and logistics.network_reputation_display registered for Business+. Aggregation engine and ≥3-operator anti-inference threshold land post-launch.

03

Multi-operator consolidation (Enterprise)

Cross-operator consolidation requires consent mechanics not yet specified. Migration 290 explicitly defers this — “intentionally NOT modelled here.” Tier feature key reserved.

04

OCR pipeline for BOL upload

Milestone source enum value 'ocr' is registered; the OCR service that ingests a BOL image, extracts container / vessel / seal / voyage fields, and posts the corresponding milestone is post-launch.

05

Geofence-driven auto-transitions

Dispatch state transitions on geofence entry / exit (arrived_at_pickup, arrived_at_delivery) — not currently implemented. v1 captures GPS at the moment the driver taps the milestone; auto-advance from geofence comes later.

06

Carrier API breadth — direct integrations

v1 integration registry: project44 (live), Freightos Baltic / Xeneta / Drewry / MarineTraffic / Vizion (freight-rate + AIS visibility). Direct integrations with Maersk / MSC / CMA CGM / ONE / HMM / Hapag-Lloyd, IATA Cargo IQ for air, and FedEx / UPS / DHL / USPS for express land as the underlying APIs are wired. logistics_carrier_api_credentials.carrier_name is free-text TEXT so adding a carrier doesn’t require schema changes.

07

Native iOS / Android driver app

PWA is the v1 driver surface (manifest + service worker live in client/public/). Native apps with biometric auth, native camera APIs, and push notifications are post-launch.

08

Scheduled carrier-API polling cadence

The logistics_carrier_api_credentials table has a last_polled_at column. The scheduled job that polls each connected carrier per its cadence (daily for ocean, hourly on critical air legs, 12h for rail) is post-launch.

FAQ

The questions every forwarder asks first.

No. We don’t compete with CargoWise, Magaya, Descartes, or Project44. We’re the coordination layer between your TMS and the operator who hired you. Keep your TMS for internal operations; use the Logistics Portal for the operator-side workflow that lives outside it — bookings the operator initiates, milestone visibility the operator needs, customs filings against the operator’s shipments, rate sheets and RFQs the operator queries, and freight invoices the operator pays. TMS integration for operators with CargoWise / Magaya / Descartes is on the Enterprise tier.

Send us your forwarder list and one in-flight booking. We’ll set up a sandboxed Logistics Portal and walk one of your forwarders through it in 30 minutes.

Send a CSV of your top forwarders and one open shipment. We’ll spin up a sandboxed Logistics Portal with your tenant’s branding, your forwarder list as external_organizations, and walk a real forwarder through accepting an RFQ, posting milestones, filing customs, and issuing an invoice. No demo data. Your forwarders, your shipment.

Book a demoTalk to sales

See the full Network — 4 portals, one record →

Logistics Portal · The cockpit your forwarders work in | TradeOS