Technical documentation

Routes, guards, payloads, and files as the Tenant API stands today.

Dashboard

Tenant API is the per-tenant CRM runtime. REST is registered with Fastify prefix /api/v1 (server.jsregisterRoutes). JWT tenant routes send Authorization: Bearer {{access_token}} unless a route is explicitly public.

Shipment pipeline

flowchart LR
  Lead --> Quote --> Order --> Dispatch
  Dispatch --> Invoice
  Invoice --> Payment

Envelope

  • Success: { ok: true, status, invokedEndpoint, invokedMethod, responseTime, timestamp, ...result }
  • Error: { ok: false, status, invokedMethod, responseTime, timestamp, error }
  • Do not leak SQL or stacks in production (src/core/index.js error handler).
  • Lists paginate. Role-scoped totals must match visible rows (src/common/list-scope.js).

Guards

Guard Behavior
authGuard Bearer JWT → request.user. Then IP allowlist (assertIpAllowlist).
roleGuard(roles) Requires at least one of request.user.roles.
permissionGuard(key) Requires key in request.user.permissions (example leads.read).
departmentGuard(dept) Requires request.user.department === dept.

Platform rules

  • One DB + one runtime per tenant. Owner remains source of truth for platform billing. Tenant stores a mirrored copy of those commercial transactions only.
  • Static uploads: GET /uploads/* with path traversal protection. Multipart writes use getTenantApiPublicDir().
  • Set TENANT_API_PUBLIC_ORIGIN to the browser-facing API origin (no trailing slash) so stored signed-PDF URLs are not derived from internal Host.
  • Quote → order is POST /api/v1/tenant/orders with quoteId. There is no POST /tenant/quotes/:id/convert-to-order.
  • Accounts send-invoice sets paymentStatus to invoice_sent. Order/dispatch send-invoice is email only.
  • Invoices module (/invoices) ≠ Accounts workbench. Payments module (/payments) ≠ order nested /tenant/orders/:orderId/payments ≠ Accounts paymentStatus on the order row.

Health

Method Path Access Notes
GET /health (full: /api/v1/health) Public Registered inside registerRoutes so the live path is /api/v1/health

Body includes ok, version (0.1.0), environment, timestamp, uptime, message.

curl --request GET "{{base_url}}/api/v1/health"

Uploads

  • GET /uploads/* is registered on the app without the /api/v1 prefix (server.js).
  • GET /uploads/customer-signatures/:filename/download is a dedicated download route.
  • Quote/order/dispatch attachment URLs typically live under this tree after multipart write.

Implementation map

Piece Path
Server tenant-api/src/server.js
Module register tenant-api/src/modules/index.js
Guards / envelope tenant-api/src/core/index.js
List scope src/common/list-scope.js
List filters / month window src/common/list-filters.js
Pagination schema src/common/pagination-schema.js
Public uploads helper src/common/public-paths.js
Multipart attachments src/common/multipart-attachments.js

Auth

Login issues JWT access and refresh tokens. Access tokens carry userId, tenantId, roles[], department, and permissions[]. Passwords arrive encoded from the CRM UI; the API decodes, then bcrypts. Never store plaintext.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
POST /tenant/auth/login Public
GET /tenant/auth/me authGuard
POST /tenant/auth/refresh Public (body refreshToken)
POST /tenant/auth/logout Public / session
POST /tenant/auth/request-reset Public
POST /tenant/auth/request-reset/resend Public
POST /tenant/auth/request-reset/verify-otp Public
POST /tenant/auth/reset Public
POST /tenant/auth/verify-email/request Public
POST /tenant/auth/verify-email/resend Public
POST /tenant/auth/verify-email Public
POST /tenant/auth/verify-password authGuard
PATCH /tenant/auth/password authGuard

Bodies

Endpoint Required fields
login email, password
refresh refreshToken (min 10 chars)
request-reset / resend email
verify-otp email, otp (length 6)
reset email, password (min 8), otp (length 6)
verify-email request/resend email
verify-email email, otp (length 6)
verify-password password
PATCH password password (min 8), confirmPassword (min 8)
flowchart LR
  Login --> AccessToken
  AccessToken --> Me
  Refresh --> AccessToken
  RequestReset --> VerifyOtp
  VerifyOtp --> Reset
curl --request POST "{{base_url}}/api/v1/tenant/auth/login" \
  --header "Content-Type: application/json" \
  --data-raw '{ "email": "admin@example.com", "password": "encoded-password" }'

Implementation map

Piece Path
Routes tenant-api/src/modules/auth/routes.js
Controller tenant-api/src/modules/auth/controller.js
Service tenant-api/src/modules/auth/service.js
Schemas tenant-api/src/modules/auth/schemas.js
Crypto helpers src/common/crypto-helpers.js
IP allowlist src/common/ip-allowlist.js

SSE

GET /api/v1/tenant/sse is an authenticated event stream. Heartbeats every 25 seconds (: heartbeat). First event is connected with { ok, tenantId, userId }. Helpers: sendSSEToTenant, sendSSEToUser, sendSSEToUsers. Created lifecycle events (lead.created, quote.created, order.created, dispatch.created) go to a scoped recipient set, not blindly the whole tenant.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/sse authGuard

Known events

Event Typical payload / notes
connected { ok, tenantId, userId } on connect
lead.created / lead.updated / lead.deleted / lead.assigned / lead.converted Created is scoped; assigned is sendSSEToUser
lead.vehicle.* / lead.other.* / lead.note.* Tenant broadcast from lead nested controllers
quote.created / quote.updated / quote.deleted / quote.assigned / quote.converted / quote.rejected / quote.rework_required Created/converted scoped; rejected on order reject
order.created / order.updated / order.deleted / order.assigned / order.approved / order.rejected / order.reactivated / order.requires_approval Created scoped
order.carrier_assigned / order.carrier_updated / order.carrier_removed Assign vs reassignment
order.vehicle.* / order.other.* / order.note.* / order.signature.* / order.payment.* / order.calendar_event.* Nested order resources
dispatch.created / dispatch.updated / dispatch.deleted / dispatch.assigned / dispatch.delivered / dispatch.cancelled Created scoped
dispatch.vehicle.* / dispatch.other.* / dispatch.note.* / dispatch.bol.* Nested dispatch
notify.info / notify.read / notify.all_read / notify.deleted In-app notifications
user:created / user:updated / user:activated / user:suspended / user:deleted / user:bulk-operation / user:avatar_updated Users (colon names)
user:invite:created / user:invite:resent / user:invite:cancelled / user:invite:accepted Invites
team.created / team.updated / team.deleted / team.members_updated Teams
carrier.created / carrier.updated / carrier.deleted / nested driver/insurance/trailer/otherContact Carriers
calendar.created / calendar.updated / calendar.deleted Standalone calendar
accounts.invoice_sent / accounts.payment_updated Accounts workbench
settings.updated / settings.integrations_updated Settings
email-template.created / .updated / .deleted Email templates
edoc.signed Public eDoc submit
analytics.team_dashboard.updated { team, date } after sales/dispatch mutations
activity-log.created To privileged viewers
curl --request GET "{{base_url}}/api/v1/tenant/sse" \
  --header "Authorization: Bearer {{access_token}}" \
  --header "Accept: text/event-stream"

Implementation map

Piece Path
Plugin tenant-api/src/modules/sse/index.js
Helpers src/common/sse-helpers.js
Scoped emit / notify src/common/notification-helpers.js
Team dashboard SSE src/common/team-dashboard-sse.js

Users

Staff CRUD, invites, bulk activate/suspend/delete, activate/suspend, avatar. Lists never include the requester. Higher-role users do not appear in lower-role lists (ROLE_HIERARCHY in users/utils.js). Commission fields apply to sales and dispatch roles.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/users/stats authGuard + users.read
GET /tenant/users authGuard + users.read
PATCH /tenant/users/me/avatar authGuard (self)
POST /tenant/users authGuard + roleGuard(admin,sub-admin) + users.create
GET /tenant/users/:id authGuard + users.read
PATCH /tenant/users/:id authGuard + users.update
PATCH /tenant/users/password/reset-by-email authGuard + roleGuard(admin,sub-admin)
POST /tenant/users/:id/activate authGuard + roleGuard(admin,sub-admin) + users.update
POST /tenant/users/:id/suspend authGuard + roleGuard(admin,sub-admin) + users.update
DELETE /tenant/users/:id authGuard + roleGuard(admin) + users.delete
POST /tenant/users/bulk authGuard + roleGuard(admin,sub-admin) + users.update
POST /tenant/users/invites authGuard + roleGuard(admin,sub-admin) + users.create
GET /tenant/users/invites authGuard + users.read
POST /tenant/users/invites/:inviteId/resend authGuard + roleGuard(admin,sub-admin) + users.create
POST /tenant/users/invites/:inviteId/cancel authGuard + roleGuard(admin,sub-admin) + users.update
POST /tenant/users/invites/accept Public

List / query fields

  • search, status (active | suspended), departmentId, roleId
  • dateFrom, dateTo
  • limit, offset
  • orderBy: createdAt | updatedAt | lastLoginAt | firstName | lastName | email
  • Invite list: status (pending | accepted | cancelled, default pending), search, limit, offset

Required create fields

  • Create user: email, password (min 8), firstName, lastName. Optional: departmentId, roleIds[], commissionType (percentage | fixed) / commissionValue (aliases commisionType / commisionValue)
  • Invite: email, firstName, lastName. Optional departmentId, roleIds[]
  • Accept invite: token (uuid), password (min 8)
  • Bulk: userIds[] (min 1), action (activate | suspend | delete)
  • Admin reset-by-email: email, password (min 8)

Enums / lifecycle

Field Values
User status active, suspended
Invite status pending, accepted, cancelled
Commission type percentage (0–100), fixed (0–99999)
Commission-eligible roles sales, sales-head, dispatch, dispatch-head

SSE

user:created, user:updated, user:activated, user:suspended, user:deleted, user:bulk-operation, user:avatar_updated, user:invite:created, user:invite:resent, user:invite:cancelled, user:invite:accepted.

curl --request GET "{{base_url}}/api/v1/tenant/users?limit=50&offset=0" \
  --header "Authorization: Bearer {{access_token}}"

Implementation map

Piece Path
Routes tenant-api/src/modules/users/routes.js
Controller tenant-api/src/modules/users/controller.js
Service tenant-api/src/modules/users/service.js
Schemas tenant-api/src/modules/users/schemas.js
Hierarchy / commission tenant-api/src/modules/users/utils.js

Departments

Org units used by users, teams, email templates, and some list filters. Create/delete require Admin or Sub Admin plus permission keys. Patch uses departments.update without the extra roleGuard.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/departments authGuard + departments.read
POST /tenant/departments authGuard + roleGuard(admin,sub-admin) + departments.create
GET /tenant/departments/:id authGuard + departments.read
PATCH /tenant/departments/:id authGuard + departments.update
DELETE /tenant/departments/:id authGuard + roleGuard(admin,sub-admin) + departments.delete

List / query fields

  • limit (default 50)
  • offset (default 0)
  • orderBy / orderDir (ASC | DESC, default DESC)
  • search
  • dateFrom / dateTo (ISO datetime)
  • month + year together (month 1–12). Date range wins when either dateFrom or dateTo is sent
  • List windows default to the current calendar month when no explicit range is sent (resolveListCreatedAtRange)

Required create fields

  • name (1–80 chars). Optional description.

Implementation map

Piece Path
Routes tenant-api/src/modules/departments/routes.js
Schemas tenant-api/src/modules/departments/schemas.js

Teams

Teams own members, heads (roleInTeam: head | member), and sourceOrigins. Team origins win over tenant Settings origins when both match a public capture request (capture-origin.js + lead capture controller).

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/teams authGuard + teams.read
POST /tenant/teams authGuard + roleGuard(admin,sub-admin) + teams.create
GET /tenant/teams/:id authGuard + teams.read
PATCH /tenant/teams/:id authGuard + roleGuard(admin,sub-admin) + teams.update
DELETE /tenant/teams/:id authGuard + roleGuard(admin,sub-admin) + teams.delete
PUT /tenant/teams/:id/members authGuard; controller: admin/sub-admin or team head (sales-head/dispatch-head)

List / query fields

  • limit (default 50)
  • offset (default 0)
  • orderBy / orderDir (ASC | DESC, default DESC)
  • search
  • dateFrom / dateTo (ISO datetime)
  • month + year together (month 1–12). Date range wins when either dateFrom or dateTo is sent
  • List windows default to the current calendar month when no explicit range is sent (resolveListCreatedAtRange)

Required create fields

  • name (1–120). Optional: description, departmentId, sourceOrigins[] (strings), isActive (default true)
  • Replace members: { members: [{ userId, roleInTeam? }] } with roleInTeam head | member (default member)

Org-team list-scope

Lifecycle lists merge org-team visibility (mergeOrgTeamLifecycleWhere): Admin/Sub Admin unchanged; team members see team-origin records plus assigned plus created-by-self; non-team privileged heads hide team-owned records unless assigned/created.

SSE

team.created, team.updated, team.deleted, team.members_updated.

Implementation map

Piece Path
Routes tenant-api/src/modules/teams/routes.js
Controller tenant-api/src/modules/teams/controller.js
Schemas tenant-api/src/modules/teams/schemas.js
Capture origins src/common/capture-origin.js
List scope teams src/common/list-scope.js

Roles

Seeded roles include admin, sub-admin, sales, sales-head, dispatch, dispatch-head, marketing, marketing-head, accounts, accounts-head (plus admin-depart in hierarchy utils). Create attaches permissionIds.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/roles authGuard + roles.read
POST /tenant/roles authGuard + roleGuard(admin,sub-admin) + roles.create
GET /tenant/roles/:id authGuard + roles.read
PATCH /tenant/roles/:id authGuard + roles.update
DELETE /tenant/roles/:id authGuard + roleGuard(admin,sub-admin) + roles.delete

List / query fields

  • limit (default 50)
  • offset (default 0)
  • orderBy / orderDir (ASC | DESC, default DESC)
  • search
  • dateFrom / dateTo (ISO datetime)
  • month + year together (month 1–12). Date range wins when either dateFrom or dateTo is sent
  • List windows default to the current calendar month when no explicit range is sent (resolveListCreatedAtRange)

Required create fields

  • name (1–80). Optional description, permissionIds[] (uuids, default []).

Implementation map

Piece Path
Routes tenant-api/src/modules/roles/routes.js
Schemas tenant-api/src/modules/roles/schemas.js

Permissions

Keys look like leads.read or orders.update. Routes call permissionGuard('resource.action'). Create stores key, name, optional description.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/permissions authGuard + permissions.read
POST /tenant/permissions authGuard + roleGuard(admin,sub-admin) + permissions.create
GET /tenant/permissions/:id authGuard + permissions.read
PATCH /tenant/permissions/:id authGuard + permissions.update
DELETE /tenant/permissions/:id authGuard + roleGuard(admin,sub-admin) + permissions.delete

List / query fields

  • limit (default 50)
  • offset (default 0)
  • orderBy / orderDir (ASC | DESC, default DESC)
  • search
  • dateFrom / dateTo (ISO datetime)
  • month + year together (month 1–12). Date range wins when either dateFrom or dateTo is sent
  • List windows default to the current calendar month when no explicit range is sent (resolveListCreatedAtRange)

Required create fields

  • key (1–120), name (1–150). Optional description.

Permission keys used by routes (non-exhaustive)

  • users.read|create|update|delete
  • departments.*, teams.*, roles.*, permissions.*
  • leads.read|create|update|delete, quotes.*, orders.*, dispatch.*
  • sales.assign, dispatch.assign, carriers.assign
  • orders.send-invoice, dispatch.send-invoice
  • accounts.read, accounts.send_invoice, accounts.update_payment
  • invoices.*, payments.*, carriers.*, customers.read|create|update
  • analytics.view, templates.view, search.view, email-templates.read

Implementation map

Piece Path
Routes tenant-api/src/modules/permissions/routes.js
Schemas tenant-api/src/modules/permissions/schemas.js

Admin

Tenant-admin views of subscription, features, and owner-pushed templates. Owner syncs into this tenant on login and on an interval, plus a webhook. Tenant is not the authority for platform billing.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /admin/subscription authGuard + roleGuard(admin,sub-admin)
GET /admin/features authGuard + roleGuard(admin,sub-admin); query enabled optional
GET /admin/template-version authGuard + roleGuard(admin,sub-admin)
POST /admin/apply-template authGuard + roleGuard(admin,sub-admin)
GET /admin/sync authGuard + roleGuard(admin,sub-admin); query force optional boolean
POST /admin/sync/webhook Shared token (TENANT_SYNC_WEBHOOK_TOKEN), no user JWT

Bodies

  • Apply template: optional key (min 3), overwrite (default true)
  • Webhook body: optional tenantId, reason, triggeredBy, triggeredAt, timestamp

Implementation map

Piece Path
Admin routes tenant-api/src/modules/admin/routes.js
Admin schemas tenant-api/src/modules/admin/schemas.js
Sync routes tenant-api/src/modules/admin-sync/routes.js
Sync schemas tenant-api/src/modules/admin-sync/schemas.js

Leads

Authenticated lead CRUD, packages (vehicles or other), notes, history, convert-to-quote. Public capture is unauthenticated and origin-gated. Convert loads the linked Customer and writes city, state, postal, address, and country onto the new quote. Creating a quote with leadId does the same hydration.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
POST /public/leads/capture Public + x-tenant-id + Origin allowlist (tenant sourceOrigins / team sourceOrigins)
GET /tenant/leads/stats authGuard + leads.read
GET /tenant/leads authGuard + leads.read
POST /tenant/leads authGuard + leads.create + sales.assign if assignedTo set
POST /tenant/leads/:id/duplicate authGuard + leads.create
GET /tenant/leads/:id authGuard + leads.read
PATCH /tenant/leads/:id authGuard + leads.update + sales.assign if assignedTo set
DELETE /tenant/leads/:id authGuard + leads.delete
POST /tenant/leads/:id/convert-to-quote authGuard + leads.create
GET /tenant/leads/:leadId/vehicles authGuard + leads.read
POST /tenant/leads/:leadId/vehicles authGuard + package manage or leads.create
GET /tenant/leads/:leadId/vehicles/:vehicleId authGuard + leads.read
PATCH /tenant/leads/:leadId/vehicles/:vehicleId authGuard + package manage or leads.update
PUT /tenant/leads/:leadId/vehicles/:vehicleId Same as PATCH (frontend PUT)
DELETE /tenant/leads/:leadId/vehicles/:vehicleId authGuard + package manage or leads.delete
GET /tenant/leads/:leadId/others authGuard + leads.read
POST /tenant/leads/:leadId/others authGuard + package manage or leads.create
GET /tenant/leads/:leadId/others/:otherId authGuard + leads.read
PATCH /tenant/leads/:leadId/others/:otherId authGuard + package manage or leads.update
PUT /tenant/leads/:leadId/others/:otherId Same as PATCH
DELETE /tenant/leads/:leadId/others/:otherId authGuard + package manage or leads.delete
GET /tenant/leads/:leadId/internal-notes authGuard + leads.read
POST /tenant/leads/:leadId/internal-notes authGuard + leads.create
GET /tenant/leads/:leadId/internal-notes/:noteId authGuard + leads.read
DELETE /tenant/leads/:leadId/internal-notes/:noteId authGuard + leads.delete
GET /tenant/leads/:leadId/change-history authGuard + leads.read

List / query fields

  • search, stage, isActive, priority (low|normal|high), assignedTo, customer, transportType
  • originCity / originState / originZip, destinationCity / destinationState / destinationZip
  • expectedShipDateFrom / expectedShipDateTo, source, trackingNumber
  • dateFrom / dateTo, month+year
  • limit, offset
  • orderBy: createdAt | updatedAt | expectedShipDate | expectedDeliveryDate | amount | priority | leadNumber
  • Change history: limit, offset, orderBy (timeStamp|actionType)

Required create fields

  • Either vehicle (make, model, year) or other (type, name) — not both
  • Optional staff fields include contactName, contactEmail, contactPhone, origin/destination, transportType, assignedTo (triggers sales.assign), priority (default normal)
  • Public capture additionally requires Origin match; assignment fields in the body are ignored

Nested resources

  • Vehicles: modelYear (1900–2100), make, model. Optional: type, inop, carrierPay, brokerFee, vin, plateNumber, color, weight, mods, notes, addOn, lotNumber, status (active|inactive)
  • Others: type, name. Optional weight, dimensions, description, status
  • Internal notes: note (min 1)

Enums / lifecycle

Set Values
stage new, contacted, quoted, order, dispatch, delivered, closed, lost
state (LEAD_STATES) Hot Leads, Not Interested (NI), VM, Disconnected Number (DC), Dealer Potential, Quoted, Booked
priority low, normal, high
transportType Open, Enclosed, Driveaway, Other, DD, SDL, HS, SD, RGN, RGNE, Flat bed, Dry van, Power only, Box Truck, Sprinter Van

List-scope

  • Admin / Sub Admin / sales-head: all sales pipeline (canViewAllSalesPipeline)
  • sales: rows where assignedTo is self
  • Others: empty list (mode: none)
  • Plus org-team visibility merge

SSE

lead.created (scoped), lead.updated, lead.deleted, lead.assigned (to assignee), lead.converted + quote.created on convert, lead.vehicle.*, lead.other.*, lead.note.*.

flowchart LR
  Capture --> Lead
  StaffCreate --> Lead
  Lead --> ConvertToQuote
  ConvertToQuote --> Quote
curl --request POST "{{base_url}}/api/v1/public/leads/capture" \
  --header "Content-Type: application/json" \
  --header "Origin: https://quotes.example.com" \
  --header "x-tenant-id: {{tenant_id}}" \
  --data-raw '{ "make": "Honda", "model": "Civic", "year": 2018, "contactEmail": "jane@example.com" }'
curl --request GET "{{base_url}}/api/v1/tenant/leads?limit=50&offset=0" \
  --header "Authorization: Bearer {{access_token}}"

Implementation map

Piece Path
Routes tenant-api/src/modules/leads/routes.js
Schemas tenant-api/src/modules/leads/schemas.js
Stages tenant-api/src/modules/leads/constants.js
Lifecycle states src/common/lifecycle-state.js
Package access src/common/lead-quote-access.js
Capture origin src/common/capture-origin.js

Quotes

Quote CRUD, duplicate, customer email, vehicles/others, notes, history, Aqua pricing helper. POST /tenant/quotes with leadId hydrates customer fields from the lead’s customer. Multipart create/PATCH merges uploaded files with attachment URLs.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/quotes/stats authGuard + quotes.read
GET /tenant/quotes authGuard + quotes.read
POST /tenant/quotes authGuard + quotes.create + sales.assign if assigning
POST /tenant/quotes/:id/duplicate authGuard + quotes.create
POST /tenant/quotes/customer-email authGuard + quotes.update
GET /tenant/quotes/:id authGuard + quotes.read
PATCH /tenant/quotes/:id authGuard + quotes.update + sales.assign if assigning; JSON or multipart
DELETE /tenant/quotes/:id authGuard + quotes.delete
GET /tenant/quotes/:quoteId/vehicles authGuard + quotes.read
POST /tenant/quotes/:quoteId/vehicles authGuard + block dispatch package + package manage or quotes.create
GET /tenant/quotes/:quoteId/vehicles/:vehicleId authGuard + quotes.read
PATCH /tenant/quotes/:quoteId/vehicles/:vehicleId authGuard + block dispatch package + package manage or quotes.update
PUT /tenant/quotes/:quoteId/vehicles/:vehicleId Same as PATCH
DELETE /tenant/quotes/:quoteId/vehicles/:vehicleId authGuard + block dispatch package + package manage or quotes.delete
GET /tenant/quotes/:quoteId/others authGuard + quotes.read
POST /tenant/quotes/:quoteId/others authGuard + block dispatch package + package manage or quotes.create
GET /tenant/quotes/:quoteId/others/:otherId authGuard + quotes.read
PATCH /tenant/quotes/:quoteId/others/:otherId authGuard + block dispatch package + package manage or quotes.update
PUT /tenant/quotes/:quoteId/others/:otherId Same as PATCH
DELETE /tenant/quotes/:quoteId/others/:otherId authGuard + block dispatch package + package manage or quotes.delete
GET /tenant/quotes/:quoteId/internal-notes authGuard + quotes.read
POST /tenant/quotes/:quoteId/internal-notes authGuard + quotes.create
GET /tenant/quotes/:quoteId/internal-notes/:noteId authGuard + quotes.read
DELETE /tenant/quotes/:quoteId/internal-notes/:noteId authGuard + quotes.delete
GET /tenant/quotes/:quoteId/change-history authGuard + quotes.read
GET /tenant/quotes/:id/aqua-pricing authGuard + quotes.read

List / query fields

  • Common list window: search, dateFrom/dateTo, month+year, limit, offset
  • status / quoteStatus, state, leadId, assignedUser, assignedUserId, assignedTo, salesAssignedUserId, assignedTeam
  • customer, transportType, trackingNumber, quoteNumber, leadSource, approvalStatus
  • originCity/originState/originPostalCode/originZip, destination equivalents, source
  • shippingDateFrom/shippingDateTo, isActive
  • orderBy: createdAt | updatedAt | quoteCreated | shippingDate | amount | quoteNumber

Required create fields

  • With leadId: package/customer can hydrate from the lead
  • Without leadId: vehicle (make,model,year) or other (type,name), plus transportType, customerPhone, origin city/state/country, destination city/state/country, shippingDate
  • Customer email: POST /tenant/quotes/customer-email body quoteId, subject (1–998), optional message

Enums / lifecycle

Set Values
quoteStatus New, Sales Campaign, On Hold, Hot, Warm, Cold, VM, Email, Text, Red, Blue, Green, Yellow, Do not text, Booking Link Sent, Followup Campaign Set, Repeat Customer Campaign, Archived
approvalStatus draft, pending_approval, approved, rejected, cancelled
PATCH action submit, approve, reject, cancel
state union LEAD_STATES + ORDER_PROGRESS_STATES (Booked, Post to load board, Driver Assign) + DISPATCH_STATES (Picked Up, Delivered, Cancelled)

Package manage

canManageQuotePackages: admin/sub-admin/sales-head, or sales whose assignedUser matches. Dispatch roles blocked from package mutations.

List-scope

Same sales pipeline as leads: all for admin/sub-admin/sales-head; sales sees assignedUser self or quotes whose lead is assigned to self.

SSE

quote.created (scoped), quote.updated, quote.deleted, quote.assigned, quote.converted (on order create from quote), quote.rejected, quote.rework_required.

curl --request POST "{{base_url}}/api/v1/tenant/orders" \
  --header "Authorization: Bearer {{access_token}}" \
  --header "Content-Type: application/json" \
  --data-raw '{ "quoteId": "{{quote_id}}", "customerEmail": "jane@example.com" }'

Implementation map

Piece Path
Routes tenant-api/src/modules/quotes/routes.js
Schemas tenant-api/src/modules/quotes/schemas.js
Constants tenant-api/src/modules/quotes/constants.js
Package blocker src/common/package-access.js

Orders

Lifecycle after quote approval: list, create (including quote convert), patch, payment status, delivered, cancel, activate, approve/reject, assign carrier, nested packages, BOL (view-only), calendar events, signatures, nested payments, emails. orders.update is seeded for sales / sales-head so operational edits (including attachments) do not require Admin.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/orders/stats authGuard + orders.read
GET /tenant/orders authGuard + orders.read
POST /tenant/orders authGuard + orders.create (or quote-convert exception) + dispatch.assign if assignedTo and not convert
POST /tenant/orders/:id/duplicate authGuard + orders.create
GET /tenant/orders/:id authGuard + orders.read
PATCH /tenant/orders/:id authGuard + sales/sales-head or orders.update; sales-assign + dispatch-assign conditionals
PATCH /tenant/orders/:id/payment-status authGuard + roleGuard(admin,sub-admin,sales-head); body paymentStatus pending|paid
PATCH /tenant/orders/:id/delivered authGuard + orders.update
PATCH /tenant/orders/:id/cancel authGuard + admin/sub-admin/dispatch-head or orders.update
PATCH /tenant/orders/:id/activate authGuard + admin/sub-admin/sales-head/dispatch-head
DELETE /tenant/orders/:id authGuard + orders.delete
GET /tenant/orders/:orderId/vehicles authGuard + orders.read
POST /tenant/orders/:orderId/vehicles authGuard + block dispatch package + canManageOrderVehicles or orders.create
GET /tenant/orders/:orderId/vehicles/:vehicleId authGuard + orders.read
PATCH /tenant/orders/:orderId/vehicles/:vehicleId authGuard + block dispatch package + manage or orders.update
DELETE /tenant/orders/:orderId/vehicles/:vehicleId authGuard + block dispatch package + manage or orders.delete
GET /tenant/orders/:orderId/others authGuard + orders.read
POST /tenant/orders/:orderId/others Same package manage as vehicles
GET /tenant/orders/:orderId/others/:otherId authGuard + orders.read
PATCH /tenant/orders/:orderId/others/:otherId Package manage
DELETE /tenant/orders/:orderId/others/:otherId Package manage
GET /tenant/orders/:orderId/bol authGuard + canViewOrderBol
GET /tenant/orders/:orderId/bol/:bolId authGuard + canViewOrderBol
GET /tenant/orders/:orderId/calendar-events authGuard + canViewOrderCalendarEvents
POST /tenant/orders/:orderId/calendar-events authGuard + canManageOrderCalendarEvents
GET /tenant/orders/:orderId/calendar-events/:eventId view
PATCH /tenant/orders/:orderId/calendar-events/:eventId manage
DELETE /tenant/orders/:orderId/calendar-events/:eventId manage
GET/POST/PATCH/DELETE /tenant/orders/:orderId/notes and .../internal-notes view/manage/delete helpers in order-access.js; admin/sub-admin always
GET /tenant/orders/:orderId/history authGuard + orders.read
GET /tenant/orders/:orderId/signatures authGuard + orders.read
POST /tenant/orders/:orderId/signatures authGuard + orders.create
GET /tenant/orders/:orderId/signatures/:signatureId authGuard + orders.read
PATCH /tenant/orders/:orderId/signatures/:signatureId authGuard + orders.update
DELETE /tenant/orders/:orderId/signatures/:signatureId authGuard + orders.delete
GET /tenant/orders/:orderId/payments authGuard + orders.read
POST /tenant/orders/:orderId/payments authGuard + orders.create
GET /tenant/orders/:orderId/payments/:paymentId authGuard + orders.read
PATCH /tenant/orders/:orderId/payments/:paymentId authGuard + orders.update
DELETE /tenant/orders/:orderId/payments/:paymentId authGuard + orders.delete
POST /tenant/orders/:id/approve authGuard + orders.update
POST /tenant/orders/:id/reject authGuard + orders.update; body reason required
POST /tenant/orders/:id/assign-carrier authGuard + carriers.assign; body carrierId
DELETE /tenant/orders/:orderId/carrier/:carrierId authGuard + carriers.assign
POST /tenant/orders/:id/send-invoice authGuard + orders.send-invoice (email only)
POST /tenant/orders/:id/customer-email authGuard + dispatch/dispatch-head or orders.update
POST /tenant/orders/customer-email Same
POST /tenant/order/customer-email Singular alias, same access

List / query fields

  • Common window + status (ORDER_STATUSES), orderStatus, secondaryStatus, state
  • leadId, quoteId, assignedTo, assignedUser, assignedUserId, salesAssignedUserId, assignedDepartmentId, assignedRoles (comma), assignedTeam
  • customer, transportType, trackingNumber, orderNumber, customerReference, leadSource, approvalStatus, paymentStatus
  • origin/destination city/state/postal/zip, source, pickup/delivery date ranges, isActive
  • orderBy: createdAt | updatedAt | orderCreated | pickupDate | deliveryDate | amount | status | orderNumber

Required create / convert fields

  • Direct create: optional quoteId/leadId; most fields optional with defaults (orderStatus default draft, money defaults 0)
  • Convert: quoteId required + customerEmail required (convertQuoteToOrderSchema)
  • Nested payment: paymentType, amount (positive)
  • Nested calendar: title, startAt
  • Assign carrier: carrierId
  • Reject: reason (min 1)
  • Send invoice / customer email: optional emailTemplateId; customer email needs subject

Enums / lifecycle

Set Values
ORDER_STATUSES draft, awaiting_approval, awaiting_dispatch, awaiting_client_signature, scheduled, in_transit, delivered, cancelled
APPROVAL_STATUSES draft, pending_approval, approved, rejected, cancelled
PATCH payment-status (this module) pending, paidnot the Accounts enum
Carrier pay terms COD - Cash, COD - Check, Net 15, Net 30, Net 45, Net 60, Prepaid, Other
Broker fee terms Charge on Dispatch, Charge on Delivery, Charge on Pickup, Prepaid, Other
flowchart LR
  Quote --> PostOrder
  PostOrder --> PendingApproval
  PostOrder --> AwaitingDispatch
  PendingApproval --> Approve
  Approve --> AssignCarrier
  AssignCarrier --> Dispatch

List-scope

  • Admin / Sub Admin / dispatch-head: all orders
  • dispatch: assignedTo self
  • sales-head: orders owned by sales/sales-head via quote.assignedUser / lead.assignedTo (not dispatcher)
  • sales: same ownership for self only

SSE

order.created (scoped), order.updated, order.deleted, order.assigned, order.approved, order.rejected, order.reactivated, order.requires_approval, order.carrier_assigned / order.carrier_updated / order.carrier_removed, nested vehicle/other/note/signature/payment/calendar_event, plus quote.converted / quote.rejected / dispatch.deleted on related actions.

curl --request GET "{{base_url}}/api/v1/tenant/orders?limit=50&offset=0" \
  --header "Authorization: Bearer {{access_token}}"

Implementation map

Piece Path
Routes tenant-api/src/modules/orders/routes.js
Schemas tenant-api/src/modules/orders/schemas.js
Constants tenant-api/src/modules/orders/constants.js
Access helpers src/common/order-access.js
List scope src/common/list-scope.js

Dispatch

On-road records: state, secondary status, delivered, cancel, vehicles/others, notes, history, BOL (create lives here; orders BOL is view-only), invoice email and customer email. Create requires orderId.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/dispatch/stats authGuard + dispatch.read
GET /tenant/dispatch authGuard + dispatch.read
POST /tenant/dispatch authGuard + dispatch.create + dispatch.assign if assignedTo
POST /tenant/dispatch/:id/duplicate authGuard + dispatch.create
GET /tenant/dispatch/:id authGuard + dispatch.read
PATCH /tenant/dispatch/:id authGuard + dispatch.update + dispatch.assign if assignedTo; JSON or multipart
PATCH /tenant/dispatch/:id/secondary-status authGuard + dispatch.update; body secondaryStatus
PATCH /tenant/dispatch/:id/state authGuard + dispatch.update; body state, optional reason
PATCH /tenant/dispatch/:id/delivered authGuard + dispatch.update
PATCH /tenant/dispatch/:id/cancel authGuard + dispatch.update
DELETE /tenant/dispatch/:id authGuard + dispatch.delete
GET /tenant/dispatch/:dispatchId/vehicles authGuard + dispatch.read
POST /tenant/dispatch/:dispatchId/vehicles authGuard + block dispatch package + dispatch.create
GET /tenant/dispatch/:dispatchId/vehicles/:vehicleId authGuard + dispatch.read
PATCH /tenant/dispatch/:dispatchId/vehicles/:vehicleId authGuard + block dispatch package + dispatch.update
DELETE /tenant/dispatch/:dispatchId/vehicles/:vehicleId authGuard + block dispatch package + dispatch.delete
GET /tenant/dispatch/:dispatchId/others authGuard + dispatch.read
POST /tenant/dispatch/:dispatchId/others authGuard + block dispatch package + dispatch.create
GET /tenant/dispatch/:dispatchId/others/:otherId authGuard + dispatch.read
PATCH /tenant/dispatch/:dispatchId/others/:otherId authGuard + block dispatch package + dispatch.update
DELETE /tenant/dispatch/:dispatchId/others/:otherId authGuard + block dispatch package + dispatch.delete
GET /tenant/dispatch/:dispatchId/internal-notes authGuard + notes view helper
POST /tenant/dispatch/:dispatchId/internal-notes notes manage helper
GET /tenant/dispatch/:dispatchId/internal-notes/:noteId notes view
PATCH /tenant/dispatch/:dispatchId/internal-notes/:noteId notes manage
DELETE /tenant/dispatch/:dispatchId/internal-notes/:noteId notes delete helper
GET /tenant/dispatch/:dispatchId/change-history authGuard + dispatch.read
GET /tenant/dispatch/:dispatchId/bol authGuard + BOL view (via linked order)
POST /tenant/dispatch/:dispatchId/bol authGuard + canManageDispatchBol
GET /tenant/dispatch/:dispatchId/bol/:bolId BOL view
PATCH /tenant/dispatch/:dispatchId/bol/:bolId BOL manage
DELETE /tenant/dispatch/:dispatchId/bol/:bolId BOL manage
POST /tenant/dispatch/:id/send-invoice authGuard + dispatch.send-invoice (email only)
POST /tenant/dispatch/:id/customer-email dispatch/dispatch-head or dispatch.update
POST /tenant/dispatch/customer-email Same

List / query fields

  • search, status, secondaryStatus, state, orderId, carrierId, carrierName, assignedTo, assignedDepartmentId, assignedRoles
  • customer, transportType, trackingNumber, dispatchNumber, origin, destination, city/state/postal/zip, source, leadSource, primaryRep, referralSource, paymentStatus
  • pickup/delivery/ship date ranges, isActive, dateFrom/dateTo, month+year
  • orderBy: createdAt | updatedAt | status | pickupDate | deliveryDate | dispatchedAt | pickedUpAt | deliveredAt | dispatchNumber

Required create fields

  • orderId (uuid). Optional carrierId, status default confirmed, assignment, dates, money, attachments.

Enums / lifecycle

Set Values
DISPATCH_STATUSES unposted, posted, confirmed, dispatched, picked_up, in_transit, delivered, cancelled
Maps to order status unposted/postedawaiting_dispatch; confirmedawaiting_client_signature; dispatchedscheduled; picked_up/in_transitin_transit; delivered/cancelled same
DISPATCH_STATES Picked Up, Delivered, Cancelled
Secondary examples Awaiting Client Signature, Awaiting Dispatch, Dispatched

List-scope

Admin / Sub Admin / dispatch-head: all. dispatch: assignedTo self. Others: none.

SSE

dispatch.created (scoped), dispatch.updated, dispatch.deleted, dispatch.assigned, dispatch.delivered, dispatch.cancelled, nested vehicle/other/note/bol; may also emit order.approved, order.carrier_removed, order.note.*.

curl --request GET "{{base_url}}/api/v1/tenant/dispatch?limit=50&offset=0" \
  --header "Authorization: Bearer {{access_token}}"

Implementation map

Piece Path
Routes tenant-api/src/modules/dispatch/routes.js
Schemas tenant-api/src/modules/dispatch/schemas.js
Constants tenant-api/src/modules/dispatch/constants.js
Lifecycle map src/common/lifecycle-state.js

Carriers

Carrier CRUD plus nested drivers, insurance, trailers, other contacts, and attachments. Trailer delete allows Admin/Sub Admin or carriers.delete.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/carriers/stats authGuard + carriers.read
GET /tenant/carriers authGuard + carriers.read
POST /tenant/carriers authGuard + carriers.create
GET /tenant/carriers/:id authGuard + carriers.read
PATCH /tenant/carriers/:id authGuard + carriers.update
POST /tenant/carriers/:carrierId/attachments authGuard + carriers.update
DELETE /tenant/carriers/:id authGuard + carriers.delete
GET /tenant/carriers/:carrierId/drivers authGuard + carriers.read
POST /tenant/carriers/:carrierId/drivers authGuard + carriers.create
GET /tenant/carriers/:carrierId/drivers/:driverId authGuard + carriers.read
PATCH /tenant/carriers/:carrierId/drivers/:driverId authGuard + carriers.update
DELETE /tenant/carriers/:carrierId/drivers/:driverId authGuard + carriers.delete
GET /tenant/carriers/:carrierId/insurance authGuard + carriers.read
POST /tenant/carriers/:carrierId/insurance authGuard + carriers.create
GET /tenant/carriers/:carrierId/insurance/:insuranceId authGuard + carriers.read
PATCH /tenant/carriers/:carrierId/insurance/:insuranceId authGuard + carriers.update
DELETE /tenant/carriers/:carrierId/insurance/:insuranceId authGuard + carriers.delete
GET /tenant/carriers/:carrierId/trailers authGuard + carriers.read
POST /tenant/carriers/:carrierId/trailers authGuard + carriers.create
GET /tenant/carriers/:carrierId/trailers/:trailerId authGuard + carriers.read
PATCH /tenant/carriers/:carrierId/trailers/:trailerId authGuard + carriers.update
DELETE /tenant/carriers/:carrierId/trailers/:trailerId Admin/Sub Admin or carriers.delete
GET /tenant/carriers/:carrierId/other-contacts authGuard + carriers.read
POST /tenant/carriers/:carrierId/other-contacts authGuard + carriers.create
GET /tenant/carriers/:carrierId/other-contacts/:contactId authGuard + carriers.read
PATCH /tenant/carriers/:carrierId/other-contacts/:contactId authGuard + carriers.update
DELETE /tenant/carriers/:carrierId/other-contacts/:contactId authGuard + carriers.delete

List / query fields

  • Carriers: search, status (new|active|inactive|suspended|archived), limit, offset, orderBy (name|status|mcNumber|createdAt|updatedAt)
  • Drivers: search, status (active|inactive), pagination, orderBy driverName|status|createdAt|updatedAt
  • Insurance: search, status (active|expired|inactive), dateFrom/dateTo, orderBy default expirationDate
  • Trailers / other-contacts: search, status, pagination; contacts also contactType

Required create fields

  • Carrier: name (1–150). Optional MC, phones, emails, capabilities booleans, attachments[]
  • Driver: driverName
  • Insurance: policyNumber, insuranceCompany, expirationDate
  • Trailer: trailerType
  • Other contact: contactName

SSE

carrier.created / updated / deleted; carrier.driver.*; carrier.insurance.*; carrier.trailer.*; carrier.otherContact.*.

Implementation map

Piece Path
Routes tenant-api/src/modules/carriers/routes.js
Schemas tenant-api/src/modules/carriers/schemas.js

Customers

Customer book and sales assignment. Assign is sales.assign. Create allows admin/sub-admin/sales-head/sales or customers.create. Update allows admin/sub-admin/sales-head or customers.update.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/customers/stats authGuard + customers.read
GET /tenant/customers authGuard + customers.read
POST /tenant/customers authGuard + role exception or customers.create
GET /tenant/customers/:id authGuard + customers.read
PATCH /tenant/customers/:id authGuard + role exception or customers.update
PATCH /tenant/customers/:id/assign-sales authGuard + sales.assign

List / query fields

  • Common list window + isActive, state, city, postalCode / postalZip, companyName
  • orderBy: email | name | companyName | createdAt | updatedAt | leadsCount | quotesCount | ordersCount

Required create fields

  • name (1–180). Either email or phone required. Optional address fields: companyName, address, address2, city, state, postalCode, country
  • Assign sales: assignedSalesUserId (optional uuid / none token → null)

Enums

status: active | inactive.

SSE

Assigning sales can emit lead.assigned / quote.assigned to the new assignee when linked records move.

Implementation map

Piece Path
Routes tenant-api/src/modules/customers/routes.js
Schemas tenant-api/src/modules/customers/schemas.js
Sales assignee helper src/common/sales-assignee.js

Invoices

Brokerage invoice records billed against an order (orderId + amount). This is not the Accounts workbench and not Owner platform invoices.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /invoices authGuard + invoices.read
POST /invoices authGuard + invoices.create
GET /invoices/:id authGuard + invoices.read
PATCH /invoices/:id authGuard + invoices.update
DELETE /invoices/:id authGuard + invoices.delete

List / query fields

  • search, status, orderId, dateFrom, dateTo, limit, offset, orderBy (createdAt|updatedAt|dueDate|amount|status)

Required create fields

  • orderId (uuid), amount (number). Optional status (default pending), dueDate.

Implementation map

Piece Path
Routes tenant-api/src/modules/invoices/routes.js
Schemas tenant-api/src/modules/invoices/schemas.js

Payments

Brokerage payment records against an invoice row (invoiceId). This is not nested order payments (/tenant/orders/:orderId/payments) and not Accounts paymentStatus on the order.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /payments authGuard + payments.read
POST /payments authGuard + payments.create
GET /payments/:id authGuard + payments.read
PATCH /payments/:id authGuard + payments.update
DELETE /payments/:id authGuard + payments.delete

List / query fields

  • search, status, invoiceId, method, dateFrom, dateTo, limit, offset, orderBy (createdAt|updatedAt|amount|status)

Required create fields

  • invoiceId (uuid), amount. Optional method, status (default pending).

Implementation map

Piece Path
Routes tenant-api/src/modules/payments/routes.js
Schemas tenant-api/src/modules/payments/schemas.js

Accounts

Accounts workbench over orders in awaiting_client_signature (ACCOUNT_ORDER_STATUS). Permissions: accounts.read, accounts.send_invoice, accounts.update_payment. :id is an order id.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/accounts/stats authGuard + accounts.read
GET /tenant/accounts authGuard + accounts.read
GET /tenant/accounts/:id authGuard + accounts.read
POST /tenant/accounts/:id/send-invoice authGuard + accounts.send_invoice
PATCH /tenant/accounts/:id/payment-status authGuard + accounts.update_payment

List / query fields

  • Common list window + paymentStatus, paymentMethod, orderId, dispatchId, assignedTo, invoiceSentBy, transportType
  • minAmount / maxAmount, isActive
  • orderBy: createdAt | updatedAt | totalTariff | paymentStatus | invoiceSentAt | orderNumber | dispatchNumber

Bodies

  • Send invoice: optional emailTemplateId
  • Payment status: at least one of paymentStatus, commissionPaidOn, paymentDate
  • partial / paid require paidAmount > 0
  • commissionPaidOn non-null requires paymentDate in the same request
  • paymentDate must be on/after order createdAt and no later than ~2 months from today

Enums

Set Values
PAYMENT_STATUSES pending, invoice_sent, partial, paid, refunded, cancelled, overdue
PAYMENT_METHODS credit_card, debit_card, ach, wire_transfer, check, cash, zelle, paypal, other
COMMISSION_PAID_ON_OPTIONS Slot booking, On dispatch, on Pickup, after delivery fee, Partial payment

SSE

accounts.invoice_sent, accounts.payment_updated.

Implementation map

Piece Path
Routes tenant-api/src/modules/accounts/routes.js
Schemas tenant-api/src/modules/accounts/schemas.js
Constants tenant-api/src/modules/accounts/constants.js

Bulk operations

Multi-select actions from CRM tables. Ids arrays are 1–100 uuids. Each route uses the same permission the single-record action would need, plus assign permissions when an assignee is in the body.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
POST /tenant/bulk/leads/reassign authGuard + leads.update + sales.assign if assignedTo set
POST /tenant/bulk/leads/convert-to-quote authGuard + leads.create
POST /tenant/bulk/quotes/reassign authGuard + quotes.update + sales.assign if assignedUser set
POST /tenant/bulk/quotes/convert-to-order/preview authGuard + quotes.read
POST /tenant/bulk/quotes/convert-to-order authGuard + orders.create
POST /tenant/bulk/orders/reassign authGuard + orders.update + dispatch.assign if assignedTo set
POST /tenant/bulk/orders/assign-carrier/preview authGuard + orders.read
POST /tenant/bulk/orders/assign-carrier authGuard + carriers.assign
POST /tenant/bulk/dispatch/reassign authGuard + dispatch.update + dispatch.assign if assignedTo set

Bodies

Action Body
Lead reassign ids[], assignedTo (uuid|null), optional assignedTeam
Lead convert ids[], optional additionalQuoteData
Quote reassign ids[], assignedUser (uuid|null), optional assignedTeam
Quote convert preview ids[]
Quote convert items[{ quoteId, totalTariff, carrierPay, brokerFee, customerEmail?, ... }], optional shared
Order reassign ids[], assignedTo, optional assignedTeam
Assign carrier preview ids[]
Assign carrier items[{ orderId, carrierId }]
Dispatch reassign ids[], assignedTo, optional assignedTeam

SSE

Bulk convert emits quote.created / order.created via emitBulkOperationEvent. Individual reassign still uses module assignment events.

Implementation map

Piece Path
Routes tenant-api/src/modules/bulk-operations/routes.js
Schemas tenant-api/src/modules/bulk-operations/schemas.js

Marketing

Campaigns and list imports. Authenticated only (authGuard); no permissionGuard on these routes. Import completion can create leads and emit lead.created.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /marketing/campaigns authGuard
POST /marketing/campaigns authGuard
PATCH /marketing/campaigns/:id authGuard
DELETE /marketing/campaigns/:id authGuard
GET /marketing/imports authGuard
POST /marketing/imports authGuard
PATCH /marketing/imports/:importId authGuard

Required create fields

  • Campaign: name, channel. Optional status (default draft), budget, startAt, endAt
  • Start import: source
  • Update import: optional status, row counts, leads[{ title, source?, amount?, notes? }]

SSE

Import-created leads emit lead.created to the tenant from the marketing controller.

Implementation map

Piece Path
Routes tenant-api/src/modules/marketing/routes.js
Schemas tenant-api/src/modules/marketing/schemas.js

Calendar

Standalone events at /calendar/events. Order-nested calendar lives under /tenant/orders/:orderId/calendar-events (see Orders). Standalone routes are authGuard only.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /calendar/events authGuard
POST /calendar/events authGuard
PATCH /calendar/events/:id authGuard
DELETE /calendar/events/:id authGuard

List / query fields

  • userId, from, to (ISO datetime). No pagination schema on this list.

Required create fields

  • title, startAt. Optional description, endAt, location, userId.

SSE

calendar.created ({ id, ... }), calendar.updated, calendar.deleted. Order nested events use order.calendar_event.*.

Implementation map

Piece Path
Routes tenant-api/src/modules/calendar/routes.js
Schemas tenant-api/src/modules/calendar/schemas.js
Reminder job tenant-api/src/jobs/calendar/send-reminders.js

Notifications

In-app notifications plus Web Push subscribe/unsubscribe and preferences. VAPID public key is on Settings (GET /tenant/settings/vapid-public-key). Default list limit is 10, not 50.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/notifications/stats authGuard
PATCH /tenant/notifications/read-all authGuard
GET /tenant/notifications authGuard
GET /tenant/notifications/:id authGuard
POST /tenant/notifications authGuard
PATCH /tenant/notifications/:id/read authGuard
DELETE /tenant/notifications/:id authGuard
POST /tenant/notifications/push/subscribe authGuard
POST /tenant/notifications/push/unsubscribe authGuard
GET /tenant/notifications/push/subscriptions authGuard
GET /tenant/notifications/push/preferences authGuard
PATCH /tenant/notifications/push/preferences authGuard

List / query fields

  • status (all|read|unread, default all)
  • userId
  • limit (default 10), offset
  • search

Required create / push fields

  • Create: title. Optional userId (null = tenant-wide), category (default general), body, path, data
  • Mark read: { read: boolean } default true
  • Push subscribe: endpoint (url), keys.p256dh, keys.auth

SSE

notify.info, notify.read, notify.all_read, notify.deleted. Notification-service also pushes notify.info per recipient.

Implementation map

Piece Path
Routes tenant-api/src/modules/notifications/routes.js
Schemas tenant-api/src/modules/notifications/schemas.js
Notification service src/common/notification-service.js

Email templates

List is open to any authenticated user. Get-by-id requires email-templates.read. Create/update/delete require Admin, Sub Admin, or a department head role (sales-head, dispatch-head, marketing-head, accounts-head). Inactive templates are filtered in the list service unless status=all.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/email-templates authGuard
GET /tenant/email-templates/:id authGuard + email-templates.read
POST /tenant/email-templates authGuard + roleGuard(admin, sub-admin, *-head)
PATCH /tenant/email-templates/:id same roleGuard
DELETE /tenant/email-templates/:id same roleGuard

List / query fields

  • status (all|active|inactive, default active)
  • search
  • limit (1–100, default 50), offset
  • orderBy title|updatedAt|createdAt (default updatedAt)

Required create fields

  • title (1–255). Optional payload object (default {}), status (active|inactive, default active).

SSE

email-template.created, email-template.updated, email-template.deleted.

Implementation map

Piece Path
Routes tenant-api/src/modules/email-templates/routes.js
Schemas tenant-api/src/modules/email-templates/schemas.js
Constants tenant-api/src/modules/email-templates/constants.js

Files

Structured file metadata at /api/v1/files. This is not the static /uploads/* tree used by quote/order/dispatch attachments.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /files authGuard
POST /files authGuard
PATCH /files/:id authGuard
DELETE /files/:id authGuard

List / query fields

  • tag optional.

Required create fields

  • originalName, mimeType, size. Provide storageKey or bufferBase64. Optional tags[]. PATCH only updates tags.

Implementation map

Piece Path
Routes tenant-api/src/modules/files/routes.js
Schemas tenant-api/src/modules/files/schemas.js

Search

Global search, recent searches, and lifecycle-number lookup. All three require search.view. Results are permission- and list-scope-filtered (leads/quotes/orders/dispatch follow the same assignee rules as list endpoints).

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/search authGuard + search.view
GET /tenant/search/recent authGuard + search.view
GET /tenant/search/lifecycle-number authGuard + search.view

Query fields

  • Global: term (default ''), limit (1–25, default 10)
  • Recent: limit (1–20, default 10), daysAgo (1–30, default 30)
  • Lifecycle number: at least one of number, lead_number, quote_number, order_number, dispatch_number, tracking_number; limit per entity table (1–500, default 100)

Entity buckets include leads, quotes, orders, dispatches, carriers, customers, invoices, knowledge base, campaigns, calendar events, users, plus nav modules filtered by permission.

curl --request GET "{{base_url}}/api/v1/tenant/search?term=civic&limit=10" \
  --header "Authorization: Bearer {{access_token}}"

Implementation map

Piece Path
Routes tenant-api/src/modules/search/routes.js
Schemas tenant-api/src/modules/search/schemas.js
Service tenant-api/src/modules/search/service.js

Knowledge base

Internal articles. authGuard only. Prefix is /api/v1/knowledge-base/entries, not under /tenant/.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /knowledge-base/entries authGuard
POST /knowledge-base/entries authGuard
PATCH /knowledge-base/entries/:id authGuard
DELETE /knowledge-base/entries/:id authGuard

List / query fields

  • category, tag

Required create fields

  • title, content. Optional tags[], category.

Implementation map

Piece Path
Routes tenant-api/src/modules/knowledge-base/routes.js
Schemas tenant-api/src/modules/knowledge-base/schemas.js

AI pricing

Quote helper. Route preHandler is authGuard; the controller also calls authGuard and requires tenant context. Configure the AI key in Settings integrations (ai_api_key, stored encoded). Related helper: GET /tenant/quotes/:id/aqua-pricing (quotes.read).

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
POST /ai-pricing/quote authGuard

Required body fields

  • origin, destination. Optional make, model, year (1900–2100), type, brokerFee (min 50), quoteId.

Implementation map

Piece Path
Routes tenant-api/src/modules/ai-pricing/routes.js
Schemas tenant-api/src/modules/ai-pricing/schemas.js

Analytics

Role-aware dashboards. All routes require analytics.view. Team dashboard SSE (analytics.team_dashboard.updated) is emitted from sales/dispatch mutations, not from these GET routes.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/analytics/dashboard authGuard + analytics.view
GET /tenant/analytics/departments authGuard + analytics.view
GET /tenant/analytics/roles authGuard + analytics.view
GET /tenant/analytics/users/me authGuard + analytics.view
GET /tenant/analytics/users/:userId authGuard + analytics.view

Dashboard query fields

  • shipmentStatisticsYear, shippingLocationYear, shippingLocationMonth
  • revenueYear, commissionStatisticsYear, commissionStatisticsMonth
  • statsYear, statsMonth

SSE

analytics.team_dashboard.updated with { team: sales|dispatch, date } (tenant timezone default America/Chicago).

Implementation map

Piece Path
Routes tenant-api/src/modules/analytics/routes.js
Schemas tenant-api/src/modules/analytics/schemas.js
Team dashboard SSE src/common/team-dashboard-sse.js

Nav counts

Pipeline totals for CRM sidebar badges (leads, quotes, orders, dispatch). authGuard only. Each bucket is filled only if the JWT has the matching *.read permission; otherwise that key stays 0. Totals use the same stats/list-scope as the module lists (active pipeline defaults).

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/nav-counts authGuard

Response data shape: { leads, quotes, orders, dispatch } numbers.

curl --request GET "{{base_url}}/api/v1/tenant/nav-counts" \
  --header "Authorization: Bearer {{access_token}}"

Implementation map

Piece Path
Routes tenant-api/src/modules/nav-counts/routes.js
Service tenant-api/src/modules/nav-counts/service.js

Templates

Owner-published document templates cached on the tenant. View, diff, history. Apply is on Admin (POST /admin/apply-template), not here.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/templates authGuard + templates.view
GET /tenant/templates/diff authGuard + templates.view
GET /tenant/templates/history authGuard + templates.view
GET /tenant/templates/:key authGuard + templates.view

Query fields

  • List: type optional
  • History: since, limit (numeric)

Implementation map

Piece Path
Routes tenant-api/src/modules/templates/routes.js
Schemas tenant-api/src/modules/templates/schemas.js

Settings

Tenant settings, notification channels, integrations (stored encoded; returned encoded), VAPID key, and sourceOrigins / source_origins for public lead capture. Origin values are scheme + host, no path. Team sourceOrigins override tenant origins when both match.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/settings authGuard
GET /tenant/settings/email-template-department-keys authGuard
PATCH /tenant/settings authGuard + roleGuard(admin,sub-admin)
PATCH /tenant/settings/notifications authGuard + roleGuard(admin,sub-admin)
PATCH /tenant/settings/integrations authGuard + roleGuard(admin,sub-admin)
GET /tenant/settings/vapid-public-key authGuard

PATCH fields

  • Settings: commissionValue, commissionType (flat|percentage), organizationName, ownerName, timezone, ipRestrictionEnabled, allowedIps[] (max 100), companyInformation{...}, sourceOrigins[] / source_origins[] (urls), notifications{ email, inApp, push }, integrations
  • Notifications: email, inApp, push booleans
  • Integrations: ai_api_key, zoom{ apiKey, apiSecret, accountId }, googleMaps{ apiKey }, emailService{ provider, apiKey, fromEmail, fromName, smtpHost, smtpPort, user, password }

SSE

settings.updated, settings.integrations_updated.

Implementation map

Piece Path
Routes tenant-api/src/modules/settings/routes.js
Schemas tenant-api/src/modules/settings/schemas.js
Constants tenant-api/src/modules/settings/constants.js

Public profiles

Unauthenticated profile by public unique id (the personal URL slug). No authGuard. Invalid slug format → 400; missing → 404.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/public/profiles/:uniqueId Public

Response includes data.user (presented profile + uniqueId, successDispatches, roles) and organizationName.

Implementation map

Piece Path
Routes tenant-api/src/modules/public-profiles/routes.js
Unique id format tenant-api/src/modules/users/unique-id.js

eDocs

Send a sign link from the CRM (quotes.update). Public verify/submit/me/refresh are origin-gated for the eDocs site. Session JWT must have tokenType === 'edoc_access' and edocId for /public/me.

flowchart LR
  SendLink --> OpenDoc
  OpenDoc --> VerifyCustomer
  VerifyCustomer --> Sign
  Sign --> SignedPdf

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
POST /tenant/edocs-platform/send-sign-link authGuard + quotes.update
GET /tenant/edocs-platform/public/:edocNumber Public, origin-gated (edocNumber 10 alphanumeric)
POST /tenant/edocs-platform/public/:edocNumber/submit Public, origin-gated; body submission object
POST /tenant/edocs-platform/public/:id/verify-customer Public, origin-gated; id uuid; email or phone + edocNumber
POST /tenant/edocs-platform/public/auth/refresh Public, origin-gated; refreshToken min 20
GET /tenant/edocs-platform/public/me Public origin + eDoc access JWT

SSE

On signed submit: edoc.signed plus quote.updated / order.updated / lead.updated / dispatch.updated when those ids exist on the eDoc.

Implementation map

Piece Path
Routes tenant-api/src/modules/edocs-platform/routes.js
Schemas tenant-api/src/modules/edocs-platform/schemas.js
Signed PDF tenant-api/src/modules/edocs-platform/edoc-signed-pdf.js

Desktop app

Authenticated download of the Windows installer. File is public/uploads/dispatchly-v1.0.0.exe. 404 if missing. Content-Disposition: attachment.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /desktop-app/download authGuard
curl --request GET "{{base_url}}/api/v1/desktop-app/download" \
  --header "Authorization: Bearer {{access_token}}" \
  --output dispatchly-v1.0.0.exe

Implementation map

Piece Path
Routes tenant-api/src/modules/desktop-app/routes.js
Controller tenant-api/src/modules/desktop-app/controller.js

Activity logs

Privileged audit of user actions. List is Admin / Sub Admin only. Creates also SSE activity-log.created to privileged viewers.

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/activity-logs authGuard + roleGuard(admin,sub-admin)

Implementation map

Piece Path
Routes tenant-api/src/modules/activity-logs/routes.js
Service tenant-api/src/modules/activity-logs/service.js

Request logs

HTTP request audit for Admin / Sub Admin. Filter by method, status, risk flags. status list-query key is omitted so it does not collide with HTTP status filtering (statusCode).

Routes

All paths below are relative to /api/v1 unless noted. Send Authorization: Bearer {{access_token}} on every authGuard route.

Method Path Access
GET /tenant/request-logs authGuard + roleGuard(admin,sub-admin)
GET /tenant/request-logs/:id authGuard + roleGuard(admin,sub-admin)

List / query fields

  • Common window (search, dateFrom/dateTo, month+year, limit, offset) minus status
  • method (max 12 chars), statusCode, flaggedOnly (true/false), riskMin (0–100)
  • orderBy: createdAt | riskScore

Implementation map

Piece Path
Routes tenant-api/src/modules/request-logs/routes.js
Schemas tenant-api/src/modules/request-logs/schemas.js