Skip to content

API Contracts

Purpose

This file defines active, framework-independent endpoint contracts and envelopes. Historical TYPE/ACTION module notes are retained only where a legacy module shell still exists.

Endpoint Inventory

Endpoint Path Method Purpose
module read app/systems/<SYSTEM>/<module>/query.php GET/POST legacy module-owned reads where still present
module write app/systems/<SYSTEM>/<module>/post.php POST legacy module-owned writes where still present
sync app/controllers/sync.php GET temporary compatibility checksum polling from temp/<TABLE>; active app callers use /api/platform/sync/checksums
excel app/controllers/excel.php GET authenticated TMS packing-list XLSX and BMS QuickBooks CSV export
media app/controllers/media.php POST temporary compatibility adapter for upload/download/datestamp; active app callers use /api/files/media/*
gmail app/controllers/gmail.php POST temporary legacy/server-token email/PDF compatibility surface; active browser booking inquiry, packing list, and BOL dispatch callers use API v2
Gmail Add-on public/gmail/addon.php GET/POST token-protected Gmail Workspace Add-on gateway for appointment email summary, explicit item apply, and reply draft creation
AI extraction app/controllers/chatgpt.php POST legacy Gemini-backed document/OCR extraction and prompt compatibility surface; active browser extraction and prompt tasks use owner API v2 routes
API v2 api/index.php GET/POST/PATCH/PUT/DELETE framework-independent successor routes without TYPE/ACTION
Telegram webhook public/telegram/webhook.php POST Telegram Bot API message/callback handler; validates TELEGRAM_WEBHOOK_SECRET for every request, falling back to TELEGRAM_TOKEN when a separate webhook secret is not configured
Telegram cron public/telegram/cron.php or cron.php telegram_breaks GET/POST or CLI protected maintenance endpoint/CLI job for closing expired loader break queue rows and sending the next queued job Telegram when available
Gmail Add-on gateway public/gmail/addon.php GET/POST token-protected Workspace Add-on gateway for current-message appointment analysis, apply, reply draft creation, and status

Legacy Controller Status

  • app/controllers/query.php and app/controllers/post.php were removed in #454.
  • app/controllers/master.php and app/controllers/autocomplete.php were removed in #1088 after committed source scan and lookup route validation showed active callers use owner API v2 routes.
  • Active application list callers use explicit API v2 table-list routes; the shared jTable compatibility endpoint was removed after the staged cutover.
  • Active application callers must use API v2 or specialized controllers listed above, not shared TYPE/ACTION read/write dispatch.
  • Direct module endpoint shells that still exist are legacy implementation surfaces and must not be used by new API v2 clients.
  • app/controllers/media.php remains a temporary compatibility surface only; active upload, download, and datestamp callers use /api/files/media/*.
  • app/controllers/sync.php remains a temporary compatibility surface only; active checksum polling callers use /api/platform/sync/checksums.
  • app/controllers/gmail.php remains a temporary compatibility surface for server-token or legacy email/PDF callers. Active browser booking inquiry, packing-list email, and BOL email dispatch use owner API v2 routes.
  • app/controllers/chatgpt.php remains a temporary compatibility surface for direct legacy/external AI callers. The shared browser fallback surface was removed in #1087, so browser code must use owner API v2 routes or fail visibly for unsupported AI task/prompt names.
  • app/controllers/excel.php remains a specialized authenticated download surface for TMS packing-list XLSX and BMS QuickBooks CSV exports. The QuickBooks CSV branch also updates T_TRANSACTION.CHECK_NUMBER, so it is not a read-only API v2 download candidate without an explicit owner command contract.
  • Write handlers that reuse legacy helpers still flush sync_tasks[] through app/shared/sync/task_registry.php.

API v2 Successor Contract

API v2 is the current framework-independent successor contract. It is not a compatibility layer over legacy TYPE/ACTION.

API v2 contract rules:

  • API v2 HTTP routes enter through /api/... and are dispatched by api/index.php.
  • API v2 shared helpers live under app/shared/api/; route definitions live in app/platform/api_routes.php, app/shared/*/api_routes.php, app/addons/*/api_routes.php, or app/systems/*/*/api_routes.php.
  • Request bodies must not use TYPE, ACTION, or USERNAME as contract fields.
  • Route definitions carry explicit module ownership and must not depend on moduleGuardEndpointModuleKey().
  • API v2 success responses use { "data": ..., "requestId": "..." }.
  • API v2 errors use { "error": { "code": "...", "message": "..." }, "requestId": "..." }.
  • API v2 list routes may support LIMIT, OFFSET, PAGE, and COUNT_TOTAL=1 for server-side pagination; ITEMS_ONLY=1 omits the legacy duplicate value array and returns items plus metadata such as total, page, pageSize, startIndex, and hasMore.
  • API v2 response helpers must preserve binary-safe JSON behavior equivalent to JSON_INVALID_UTF8_SUBSTITUTE.
  • File/media API v2 work must preserve hybrid EFS/S3 T_FILE behavior from #460.
  • API v2 route matching currently supports exact paths and regex patterns; unsupported methods return HTTP 405 with Allow.
  • API v2 auth uses the existing session (login_username) and module gating uses explicit route moduleKey with getClientId().
  • Staff time event creation may perform a silent outbound QuickBooks Time sync after the CTC staff-time write has committed when QUICKBOOKS_TIME_ACCESS_TOKEN is configured. Local staff-time writes remain the source of truth; QuickBooks Time failures, skipped states, and manual-review states are logged under integrations/quickbooks-time and must not remove the local event or add integration fields to the UI/API response.
  • QuickBooks Time staff-time sync maps CLOCK_IN to an active regular timesheet, CLOCK_OUT to ending the active staff-time timesheet, BREAK_IN to ending the active regular timesheet and opening an active break timesheet, and BREAK_OUT to ending the active break timesheet and opening a new active regular timesheet. Existing QuickBooks Time behavior is preserved by allowing QUICKBOOKS_TIME_REGULAR_JOBCODE_ID=0 for no-jobcode regular work and by using existing QuickBooks Time break jobcodes for break/lunch. Staff-time current DAY and WEEK totals count 10MIN BREAK as paid time and exclude LUNCH BREAK as unpaid time. External active timesheets outside the configured staff-time model are left unchanged and logged as manual_review_required.

QuickBooks Time staff-time sync config:

  • QUICKBOOKS_TIME_ACCESS_TOKEN: QuickBooks Time OAuth bearer token or API Add-On access token. If this is empty, sync is skipped silently.
  • QUICKBOOKS_TIME_CLIENT_ID, QUICKBOOKS_TIME_CLIENT_SECRET, QUICKBOOKS_TIME_REDIRECT_URI, QUICKBOOKS_TIME_PUBLIC_BASE_URL, QUICKBOOKS_TIME_CTC_CLIENT_ID: optional OAuth setup values for the public production app URLs. QUICKBOOKS_TIME_SETUP_TOKEN is recommended for initial production setup authorization only while no QuickBooks Time access token is configured for the tenant; reconnect after a tenant token exists requires an authenticated CTC tenant administrator or super admin session. When OAuth succeeds, the access token is stored in T_TENANT_CONFIG.QUICKBOOKS_TIME_ACCESS_TOKEN for the configured CTC client. Staff-time sync still reads QUICKBOOKS_TIME_ACCESS_TOKEN through tenant config first and env fallback second. If QUICKBOOKS_TIME_REFRESH_TOKEN is present and expiry metadata is missing or expiring, sync refreshes OAuth tokens before calling QuickBooks Time.
  • QuickBooks Time user identity is stored on the staff user record as T_USER.QUICKBOOKS_TIME_USER_ID; if the value is missing, sync is skipped/logged for that staff event.
  • QUICKBOOKS_TIME_REGULAR_JOBCODE_ID: QuickBooks Time jobcode for regular loader work. Use 0 to preserve the existing no-jobcode regular clock-in/out behavior.
  • QUICKBOOKS_TIME_BREAK_10_JOBCODE_ID, QUICKBOOKS_TIME_LUNCH_JOBCODE_ID: existing QuickBooks Time break jobcodes for 10-minute and lunch breaks.
  • QUICKBOOKS_TIME_BASE_URL: defaults to https://rest.tsheets.com/api/v1.
  • QUICKBOOKS_TIME_TIMEOUT_SECONDS: defaults to 2 and is capped at 10; sync runs after the local response is scheduled and must not block the local write path.
  • QUICKBOOKS_TIME_WEEK_START_DAY: day name used by staff-time current state to compute the Time Clock WEEK total; defaults to SUNDAY and accepts SUNDAY through SATURDAY.

QuickBooks Time production app URLs:

  • /quickbooks-time/connect: public connect/reconnect page that starts OAuth authorization when app credentials are configured and the request has a tenant administrator session, super admin session, or setup token before any tenant QuickBooks Time access token is configured.
  • /quickbooks-time/callback: public OAuth redirect page that validates state and exchanges the authorization code.
  • /quickbooks-time/disconnect: public disconnect page for Intuit app configuration; GET renders a landing page only, and POST clears tenant-stored QuickBooks Time token metadata only after setup-token authorization or tenant administrator/super admin authorization with a valid session CSRF token.
  • /api/quickbooks-time/connect, /api/quickbooks-time/callback, and POST /api/quickbooks-time/disconnect: API counterparts for setup checks.
  • /privacy-policy and /terms-of-use: public legal URLs for production app review.

Gmail Workspace Add-on gateway:

  • public/gmail/addon.php is a public integration endpoint for Apps Script and is not an API v2 session route.
  • Requests must send Authorization: Bearer <GMAIL_ADDON_TOKEN> or X-CTC-Gmail-Addon-Token; query-string tokens are not accepted.
  • The Apps Script project must set GMAIL_ADDON_TOKEN and may set GMAIL_ADDON_API_URL; the latter points at https://ctclogistic.com/public/gmail/addon.php in production.
  • Success responses use { "data": ..., "requestId": "..." }; errors use { "error": { "code": "...", "message": "..." }, "requestId": "..." }.
  • Supported actions are status, analyze, apply, and reply-draft; reply_draft and draft are accepted aliases for reply-draft.
  • GET action=status returns mailbox/watch setup state and supported actions.
  • POST action=analyze requires gmailMessageId and returns the parsed appointment request summary, item list, safe item ids, reply-draft-ready item ids, safe/review counts, and reviewUrl for the CTC system review page. If the message has not been ingested yet, the gateway may ingest the Gmail message through the configured mailbox OAuth token before returning the summary; an already-ingested message with no actionable rows must not be re-ingested unless forceRefresh is true. The optional preferFast flag is intended for interactive Gmail add-on selection/refresh flows and may use deterministic table extraction before falling back to Gemini.
  • POST action=apply accepts itemIds, outcome, optional edits, and replyMode (NONE or DRAFT) for explicit item-level handling by the add-on.
  • POST action=reply-draft accepts gmailMessageId or itemIds and creates same-thread Gmail drafts only for selected resolved items that have no existing reply message.
  • POST action=process-safe is not an active gateway action. Apps Script safe-processing UI must call apply with backend-selected item ids, or a separate alias must be implemented before it is documented as supported.
  • GMAIL_ADDON_CLIENT_ID optionally pins the tenant context and defaults to Config::CLIENT_ID.
  • GMAIL_ADDON_SYSTEM_BASE_URL and GMAIL_ADDON_REVIEW_PATH may override the system link used for Review in System; defaults are the CTC app base URL and /transload-email-approval.

The active legacy-to-v2 route inventory and deletion conditions live in .agents/rules/API_V2_CONTRACT_INVENTORY.md. The conditional React-specific proposal is retained in .agents/rules/REACT_API_CLIENT.md; it has no current adoption authority. The final cutover validation runbook lives in docs/runbooks/API_V2_CUTOVER_VALIDATION_RUNBOOK.md.

KJTable API v2 list contract

  • Active KJTable schemas set dataSource.adapter to api-v2 and provide an explicit endpoint beginning with /api/; active callers must not send type, TYPE, or ACTION.
  • List requests use GET with offset, limit, optional sort/direction, optional from/to, and column filters encoded as filter[<column>].
  • Successful responses use { "data": { "items": [...], "total": <number>, "checksums": {...}, "query": <string|null> }, "requestId": "..." }. Both items and a non-negative numeric total are required; malformed 2xx payloads are visible adapter errors, not empty lists.
  • Each route definition pins its internal legacy query type and its moduleKey server-side. Neither value is accepted from the browser, preserving the existing query, effective-tenant, permission, filter, sort, paging, checksum, and refresh behavior during the staged cutover.
  • Active route families are /api/platform/table-lists/*, /api/yms/table-lists/*, /api/tms/table-lists/*, /api/oms/table-lists/*, and /api/bms/table-lists/*.

Active API v2 routes:

Route Method Module key Purpose
/api/platform/ping GET, POST none authenticated platform/session and JSON parsing smoke
/api/platform/account/password PATCH none current-session password change without legacy ACCOUNT contract
/api/platform/sync/checksums GET none authenticated checksum polling over temp/<TABLE> tokens without using the legacy sync.php controller
/api/platform/menu/tree GET, PATCH platform.menu visible menu tree read and reorder/update; permission-only route rows are excluded; write clears KJ_MENU_CACHE
/api/platform/menu/favorites GET, POST none current-user sidebar favorite menu list/add; permission-only rows are excluded; available to admin/office-style roles and stores favorites by user/menu UUID mapping
/api/platform/menu/favorites/{id} DELETE none soft-delete a current-user sidebar favorite by numeric menu id
/api/platform/menu/items POST platform.menu create menu item
/api/platform/menu/items/{id} PATCH, DELETE platform.menu update/delete menu item
/api/platform/menu/roles GET platform.menu role list for menu permissions
/api/platform/menu/roles/{id}/default-menu PATCH platform.menu update role default menu
/api/platform/module-pages GET none page registry list for menu/admin tools
/api/platform/table-lists/{chassis,users,commodities,companies,drivers,master-data,carriers} GET route-specific explicit platform/master list reads for the KJTable API v2 adapter
/api/yms/table-lists/* GET route-specific explicit YMS import/export/container/equipment list reads for the KJTable API v2 adapter
/api/tms/table-lists/{transloads,deliveries,loading-planner,driver-logs} GET route-specific explicit TMS list reads for the KJTable API v2 adapter
/api/oms/table-lists/{bookings,customer-pos} GET oms.inbound-order explicit OMS list reads for the KJTable API v2 adapter
/api/bms/table-lists/* GET route-specific explicit BMS transaction/rate/scale-ticket/fuel list reads for the KJTable API v2 adapter
/api/platform/users POST none create tenant user without generic T_USER write
/api/platform/users/{id} GET, PATCH none tenant user detail/update without exposing password hash
/api/platform/users/roles GET none user role options
/api/platform/users/check-username GET none username availability check
/api/platform/companies GET, POST none tenant company list/create for company popup flows
/api/platform/companies/options GET none tenant company option list for master-data replacement flows; supports bounded search, company type filter, and optional blank value
/api/platform/companies/search GET none tenant company autocomplete search without legacy TYPE; returns suggestions plus raw items
/api/platform/companies/form POST, PATCH none company popup form update/delete for company and company-email rows
/api/platform/companies/{id} GET none tenant company detail with email rows
/api/platform/master-data/form POST, PATCH table-specific menu role generic popup form create/update/delete for allowlisted master-data tables; each requested table requires access to its owning menu (chassis-list, commodity-list, or master-list)
/api/platform/master-data/options GET none bounded T_MASTER category option list replacing deleted legacy master-data lookup flows
/api/platform/master-data/row GET table-specific menu role allowlisted master-data row detail for popup edit fallback; the requested table requires access to its owning menu (chassis-list, commodity-list, or master-list)
/api/platform/commodities/options GET none tenant commodity option list for master-data replacement flows, including name-only compatibility mode
/api/platform/commodities/search GET none tenant commodity autocomplete search without legacy TYPE; returns suggestions plus raw items
/api/platform/operation-events GET, POST none bounded persisted operation notification feed/create; GET defaults to recent screen-facing candidates, applies current role policy, and returns only public display fields; POST is super-admin test tooling only
/api/platform/operation-events/count GET none server-backed unread persisted operation notification count and latest visible event ID cursor for current role policy within the bounded notification window
/api/platform/operation-events/preferences GET, PATCH none current user's enabled/disabled choices for role-visible notification types; a preference can only suppress a type and never grants visibility outside the role policy
/api/platform/operation-events/ack PATCH none acknowledge/read multiple persisted operation notifications by UUID for the current role policy; this is not workflow confirmation
/api/platform/operation-events/{uuid}/ack PATCH none acknowledge/read a persisted operation notification; this is not workflow confirmation
/api/platform/operation-events/{uuid}/unack PATCH none undo acknowledgement for a persisted operation notification when the current role can acknowledge it; used only for immediate UI undo
/api/addons/telegram/messages POST addon.telegram canonical authenticated Telegram notification send
/api/addons/ai-verifications/ingate-ticket/ai-extract POST addon.reports Gemini-backed INGATE_TICKET extraction for AI verification rows
/api/platform/telegram/send POST none compatibility Telegram send route; do not add new callers
/api/platform/codex/overview GET none admin/superadmin Codex Portal overview backed by configured Codex file roots; returns accessible projects, recent active sessions, and a bounded archive slice without full transcript scans
/api/platform/codex/projects/{projectKey}/sessions GET none admin/superadmin Codex Portal session list for source=active, archive, or all; reads Codex JSONL metadata from configured file roots and does not use DB session tables
/api/platform/codex/projects/{projectKey}/sessions/{sessionId} GET none admin/superadmin Codex Portal lazy session detail; reads one matching JSONL transcript and returns display-safe terminal lines while skipping developer/system messages
/api/platform/codex/projects/{projectKey}/sessions/{sessionId}/restore POST none admin/superadmin Codex Portal archive restore reference; writes a portal-local JSON pointer under storage/codex-portal/restored and does not mutate Codex archive files
/api/platform/codex/projects/{projectKey}/sessions/{sessionId}/messages POST none admin/superadmin Codex Portal runner entrypoint for active sessions only; starts a background codex exec resume --json process for the selected session and records runner state under storage/codex-portal/runs
/api/platform/codex/projects/{projectKey}/sessions/{sessionId}/archive POST none admin/superadmin Codex Portal archive action; blocks working sessions, moves active JSONL files into the configured archive directory, and removes restored pointers without mutating archive JSONL contents
/api/platform/admin/* GET, POST, PATCH none super-admin console tenants/users/logs/monitor/integrity/db/mass-action/onboarding routes
/api/platform/admin/control-tower GET none super-admin Control Tower deployment, tenant UUID, and runtime mapping registry; registry data comes from CONTROL_TOWER_DB_*, while current-server metadata comes from the active runtime
/api/platform/admin/control-tower/deployments POST none create a Control Tower deployment/server registry row in the CONTROL_TOWER_DB_* database
/api/platform/admin/control-tower/deployments/{id} PATCH none update a Control Tower deployment/server registry row in the CONTROL_TOWER_DB_* database
/api/platform/admin/control-tower/tenants POST none create a Control Tower global tenant identity row in the CONTROL_TOWER_DB_* database
/api/platform/admin/control-tower/tenants/{id} PATCH none update a Control Tower global tenant identity row in the CONTROL_TOWER_DB_* database
/api/platform/admin/control-tower/mappings POST none create a tenant-to-deployment runtime mapping row in the CONTROL_TOWER_DB_* database
/api/platform/admin/control-tower/mappings/{id} PATCH none update a tenant-to-deployment runtime mapping row in the CONTROL_TOWER_DB_* database
/api/platform/admin/logs/codex GET none super-admin session or Authorization: Bearer <CTC_CODEX_LOG_API_TOKEN> / X-CTC-Codex-Log-Token; Codex-friendly bounded JSONL log diagnostics; supports channel, date, sinceMinutes, limit, scanLines, requestId, path, module, username, status, method, contains, errorOnly, and includeRaw filters without arbitrary file reads
/api/yms/yard-map/ping GET yms.yard-map authenticated module guard smoke
/api/yms/yard-map/sections GET yms.yard-map yard-map section geometry read model
/api/yms/yard-map/state GET yms.yard-map yard-map occupancy and read-only map state
/api/yms/yard-moves/dispatch-queue GET yms.yard-move yard dispatch queue read model, including role-policy-filtered autoAssign* alert fields for unacknowledged operation events sourced from auto-marked queue assignments
/api/yms/yard-moves/returning-empty GET yms.yard-move returning empty queue read model
/api/yms/yard-moves/assignable-users GET yms.yard-move assignable yard dispatch user list
/api/yms/yard-truck-drivers/staff-state GET yms.yard-move yard truck driver staff-time status roster with current/day totals, open break state, and claimed dispatch counts for the yard dispatch board; Armando/Ezekiel remain yard-truck-dedicated, while shared forklift/yard staff only appear when in YARD_TRUCK context or holding claimed yard dispatch work
/api/yms/yard-truck-drivers/break POST yms.yard-move manager yard dispatch break start/end command for Yard Truck Driver users using T_STAFF_TIME_EVENT.WORK_CONTEXT='YARD_TRUCK'
/api/yms/yard-moves/unmapped-entities GET yms.yard-move unmapped yard entity search/read model
/api/yms/yard-locations/occupied-items GET yms.yard-move occupied yard location item picker; optional blockingForLocation returns CY stack blockers above the target location
/api/yms/yard-locations/available GET yms.yard-move available yard locations by area type; CY L2/L3 spots blocked only by AUTO:CY_L1_TRL:<lane> are included when the L1 trailer has an active clearing queue
/api/yms/yard-locations/stack-groups GET yms.yard-move available stack group options
/api/yms/yard-locations/available-by-stack-group GET yms.yard-move available yard locations by stack group; CY L2/L3 spots blocked only by a clearing L1 trailer are eligible
/api/yms/yard-moves/preview POST yms.yard-move yard dispatch move preview for complex request bodies
/api/yms/yard-locations/reset POST yms.yard-move reset yard location occupancy for selected equipment
/api/yms/yard-locations/move POST yms.yard-move move/register an occupied yard entity
/api/yms/yard-locations/vacate POST yms.yard-move vacate a yard location
/api/yms/yard-moves/queue POST, PATCH yms.yard-move create or update yard dispatch queue items
/api/yms/yard-moves/queue/relocate-before-pull-out POST yms.yard-move create a relocate queue item immediately before a queued/scheduled pull-out parent and preserve the parent link through the system remark tag
/api/yms/yard-moves/queue-after-clear POST yms.yard-move queue a CTN/TRL/CHS move behind an active clearing move for the occupied target location
/api/yms/yard-moves/queue/reorder POST yms.yard-move reorder yard dispatch queue items
/api/yms/yard-moves/queue/cancel POST yms.yard-move cancel a queue item
/api/yms/yard-moves/queue/claim POST yms.yard-move claim a queue item
/api/yms/yard-moves/queue/unclaim POST yms.yard-move unclaim a queue item
/api/yms/yard-moves/queue/confirm POST yms.yard-move confirm a queue item
/api/yms/yard-moves/dispatch/complete POST yms.yard-move complete an active yard dispatch
/api/yms/yard-moves/* write commands POST yms.yard-move reserve, confirm, cancel, claim, and dispatch command routes backed by explicit transactions

Yard move queue destinations support exact spot codes, DROP, stack-group instructions, single-area instructions such as A AREA, and the DIP pull-out A/C AREA instruction. A/C AREA is CTN-only and must be confirmed into an available A or C spot. | /api/yms/containers/mobile | GET | yms.container-tracking | mobile container tracking list | | /api/yms/containers/empty | GET | yms.container-tracking | available empty container list for appointment/transload flows | | /api/yms/yard-containers | GET | yms.container-tracking | yard container lookup for closing/selection flows | | /api/yms/equipment-loads/search | GET | yms.container-tracking | bounded active equipment-load autocomplete search by container number; replaces deleted legacy EQUIPMENT_LOAD autocomplete flow | | /api/yms/equipment-loads/{id}/container-detail | GET | yms.container-tracking | container edit detail payload | | /api/yms/equipment-loads/container-details | GET | yms.container-tracking | multi-container edit detail payload | | /api/yms/containers/by-delivery | GET | yms.container-tracking | container detail by delivery id | | /api/yms/containers/by-equipment-load | GET | yms.container-tracking | container detail by equipment load id | | /api/yms/containers/by-deliveries | GET | yms.container-tracking | container list by delivery ids | | /api/yms/equipment/by-delivery | GET | yms.container-tracking | equipment detail by delivery id | | /api/yms/equipment-information/{id} | GET | yms.container-tracking | equipment information row detail for popup edit fallback | | /api/yms/equipment-information/form | POST, PATCH | yms.container-tracking | equipment information popup form create/update/delete | | /api/yms/equipment/{id}/repair-logs | GET | yms.container-tracking | equipment repair log list | | /api/yms/fleet-management/dashboard | GET | yms.container-tracking | fleet dashboard read model by asset type/search, including selected assets, recent activities, due/work-order/document signals, summary, and typeCounts active/inactive/total counts by asset type for sidebar filter badges | | /api/yms/fleet-management/assets | POST, PATCH | yms.container-tracking | create/update fleet asset profiles backed by T_EQUIPMENT_INFORMATION | | /api/yms/fleet-management/assets/{id} | GET | yms.container-tracking | fleet asset detail with shaped asset, activities, and missing document signals | | /api/yms/fleet-management/activities | POST, PATCH | yms.container-tracking | create/update fleet service, inspection, odometer/meter, work-order, and status activities backed by T_EQUIPMENT_ACTIVITY | | /api/yms/containers/validate-numbers | POST | yms.container-tracking | pasted container-number validation | | /api/yms/containers/validate-full-ready | POST | yms.container-tracking | full-ready validation by equipment load ids | | /api/yms/containers/form | POST, PATCH | yms.container-tracking | container popup form create/update/delete for allowed equipment tables | | /api/yms/containers/seal-photo/ai-extract | POST | yms.container-tracking | Gemini-backed CONTAINER_SEAL_PHOTO OCR for seal number extraction | | /api/yms/containers/street-turn | POST | yms.container-tracking | street-turn an import container into an empty export-ready load | | /api/yms/containers/return-bookings | PATCH | yms.container-tracking | update selected equipment return-booking links | | /api/yms/containers/mobile/{id}/empty-weight | PATCH | yms.container-tracking | mobile container empty-weight update | | /api/yms/equipment-repair-logs/{id} | GET | yms.container-tracking | equipment repair log detail for popup edit | | /api/yms/dock-scheduling/available-docks | GET | yms.dock-scheduling | available dock option list | | /api/yms/dock-scheduling/available-yard-locations | GET | yms.dock-scheduling | available yard-location option list; includes CY L2/L3 mobile-lane targets when the L1 trailer blocker has an active clearing queue | | /api/yms/dock-scheduling/planning-yard-locations | GET | yms.dock-scheduling | planning yard-location option list; includes CY L2/L3 mobile-lane targets when the L1 trailer blocker has an active clearing queue | | /api/yms/dock-scheduling/appointments | GET | yms.dock-scheduling | dock appointment list | | /api/yms/scale/schedule | GET | yms.scale | scale schedule read model | | /api/yms/scale/tickets | GET | yms.scale | scale ticket list read model | | /api/yms/scale/tickets/{id} | GET, DELETE | yms.scale | scale ticket detail read model and scale portal delete | | /api/yms/scale/check-in | POST, PATCH | yms.scale | scale check-in create/update | | /api/yms/scale/check-out | POST | yms.scale | scale check-out write | | /api/yms/scale/purchase-inbound/walk-in | POST | yms.scale | create server-normalized ad-hoc walk-in purchase inbound shell without a commercial PO | | /api/yms/scale/purchase-inbound/finalize | POST | yms.scale | purchase inbound finalization from scale | | /api/tms/transloads/planning-board | GET | tms.transload | transload planning board read model, including planner loader display, PLANNER_LAST_SENT_TIME, and role-policy-filtered autoAssign* alert fields for unacknowledged AUTO_PLANNED/AUTO_ASSIGNED operation events | | /api/tms/transloads/assignment-auto-mode | GET, PATCH | tms.transload | tenant-wide Assignment Board auto-assignment mode stored as TMS_TRANSLOAD_ASSIGNMENT_AUTO_MODE; accepts/returns manual or auto-send so one open browser cannot keep running auto assignments after another operator turns Auto off | | /api/tms/transloads/instruction-rules | GET, PATCH | tms.transload | tenant-wide appointment/reload instruction rules stored as TMS_TRANSLOAD_INSTRUCTION_RULES; TMS_TRANSLOAD_INSTRUCTION_ENABLED=0 returns { enabled: false, rules: [] } and rejects PATCH, otherwise GET falls back to built-in defaults when no rules config exists and PATCH requires at least one valid rule | | /api/tms/transloads | GET | tms.transload | transload rows by comma-separated ids for popup edit flows | | /api/tms/transloads/form | POST, PATCH | tms.transload | transload popup form create/update/delete for allowed transload table fields | | /api/tms/transloads/load-to-load-reconcile | POST | tms.transload | copy selected transload inbound final bale quantity/weight to outbound final quantity/weight | | /api/tms/transloads/{id} | GET | tms.transload | transload row detail for popup edit flows | | /api/tms/transloads/{id}/scale-ticket | PATCH | tms.transload | link a created BMS scale ticket to a transload row | | /api/tms/transloads/{id}/bol-email | POST | tms.transload | send BOL and weight email with BOL company-email recipients, scale-ticket PDF, and transload file attachments | | /api/tms/transloads/load-schedule/ai-extract | POST | tms.transload | Gemini-backed LOAD_SCHEDULE extraction through the transload owner route | | /api/tms/transloads/export-schedule/ai-extract | POST | tms.transload | Gemini-backed export-transload scheduling target extraction; availability remains client timeblock logic | | /api/tms/transloads/load-schedule/ai-prompt | GET, PATCH | tms.transload | read/save the tenant prompt for LOAD_SCHEDULE extraction | | /api/tms/loading-planner/{id} | GET, PATCH, DELETE | tms.transload | loading-planner row detail/update/delete for planner UI and loader assignment; delete cancels related planner yard queues and returns active planning statuses to ARRIVED | | /api/tms/loading-planner/form | POST, PATCH | tms.transload | loading-planner popup form create/update/delete | | /api/tms/equipment-loads/{id}/transloads | GET | tms.transload | transload rows attached to an equipment load | | /api/tms/transloads/plan-preview | POST | tms.transload | transload planner preview command | | /api/tms/transloads/plan-apply | POST | tms.transload | apply generated transload plan | | /api/tms/transloads/assign-submit | POST | tms.transload | submit loader/warehouse assignment | | /api/tms/transloads/loader-assignment | POST, PATCH | tms.transload | loader board assignment command that keeps active planner and docked transload loader in sync | | /api/tms/transloads/loader-telegram | POST | tms.transload | loader board Telegram send command that records planner LAST_SENT_TIME after successful send | | /api/tms/transloads/stuck-dip-cleanup | POST | tms.transload | force-clear a stuck started dock-in-plan planner by REL/load number when the unloaded/docked load has no active yard trailer location | | /api/tms/transloads/confirm-plan | POST | tms.transload | confirm planned transload dispatch | | /api/tms/transloads/plan | PATCH | tms.transload | edit planning fields | | /api/tms/transloads/domestic-dock-plan | PATCH | tms.transload | create/update only the active domestic pickup planner dock (T_LOADING_PLANNER.AREA_NUMBER_TO) without writing T_TRANSLOAD.WAREHOUSE_LOCATION or yard-map occupancy | | /api/tms/transloads/check-in | POST | tms.transload | planning-board check-in command | | /api/tms/transloads/mobile-check-in | POST | tms.transload | appointment-board check-in command preserving mobile carrier-master behavior | | /api/tms/transloads/dock-in | POST | tms.transload | dock trailer and sync yard occupancy | | /api/tms/transloads/dock-in-after-clear | POST | tms.transload | queue trailer dock-in behind an active clearing yard move when the selected dock is still occupied | | /api/tms/transloads/stage | PATCH | tms.transload | update staging/loading instruction | | /api/tms/transloads/start-transloading | POST | tms.transload | start outbound container loading execution | | /api/tms/transloads/start-unloading | POST | tms.transload | start inbound unloading execution | | /api/tms/transloads/complete-transloading | POST | tms.transload | complete outbound transload; Added results require no source-lot selection or inventory reservation | | /api/tms/transloads/complete-unloading | POST | tms.transload | complete inbound unloading into warehouse and return optional telegram completion-report send status | | /api/tms/transloads/request-stack-footprint-review | POST | tms.transload | loader signals that a completed unload needs Office physical stack-space review | | /api/tms/transloads/complete-unloading-spare | POST | tms.transload | disabled direct unload-to-Spare action; complete unloading first, then use the separate Spare transition | | /api/tms/transloads/complete-spare | POST | tms.transload | CTC Office UNLOADED → SPARE; preserves the existing lot, warehouse location, and placement | | /api/tms/transloads/create-domestic-pickup-unit | POST | tms.transload | create or link a missing domestic pickup equipment unit/load from a checked-in planning card | | /api/tms/transloads/undo | POST | tms.transload | undo docked/in-progress transload execution | | /api/tms/transloads/doc-submitted | PATCH | tms.transload | update document submitted flag | | /api/tms/drivers/identity | GET | tms.delivery | resolve current WordPress-linked driver identity through T_USER.USERNAME | | /api/tms/drivers/search | GET | tms.delivery | truck/forklift operator autocomplete search with optional carrier company filter; replaces deleted legacy DRIVER autocomplete flow | | /api/tms/driver-logs/current | GET | tms.delivery | current non-finished driver log by username or userId/USER_ID | | /api/tms/driver-logs/{id} | GET | tms.delivery | driver log detail row for native Driver Log editor | | /api/tms/deliveries/driver-list | GET | tms.delivery | driver-operation delivery work list; supports userId/USER_ID | | /api/tms/deliveries | GET | tms.delivery | delivery rows by comma-separated ids for popup edit flows | | /api/tms/deliveries/form | POST, PATCH | tms.delivery | delivery and driver-log popup form create/update/delete | | /api/tms/deliveries/{id} | GET | tms.delivery | delivery row detail for popup edit flows | | /api/tms/bookings | GET, POST | tms.booking | booking list/create flows | | /api/tms/bookings/search | GET | tms.booking | bounded booking autocomplete search; replaces deleted legacy BOOKING autocomplete flow | | /api/tms/bookings/drayage-empty-options | GET | tms.booking | current Export Dashboard export -trucking booking option list with terminal/company ids for Export Dispatch Drayage Queue empty creation | | /api/tms/bookings/booking-instruction/ai-extract | POST | tms.booking | Gemini-backed BOOKING_INSTRUCTION extraction through the booking owner route | | /api/tms/bookings/booking-instruction/ai-prompt | GET, PATCH | tms.booking | read/save the tenant prompt for BOOKING_INSTRUCTION extraction | | /api/tms/bookings/form | POST, PATCH | tms.booking | booking popup form create/update/delete | | /api/tms/bookings/{id} | GET, PATCH | tms.booking | booking detail/update flows | | /api/tms/bookings/{id}/packing-list/email | POST | tms.booking | send packing list email to customer, inspector, or both with PACKING_LIST company-email recipients and split ZIP attachments | | /api/tms/empty-plan/summary | GET | tms.booking | empty container booking planning read model | | /api/tms/empty-plan/create | POST | tms.booking | create empty-container booking allocations from the empty-plan page | | /api/tms/empty-plan/booking-inquiry-email | POST | tms.booking | send empty-plan booking-needed inquiry emails grouped by billing company after revalidating selected summary keys against the current empty-plan read model | | /api/tms/carriers/{id} | GET | tms.carrier | carrier master row detail for popup edit flows | | /api/tms/carriers/form | POST, PATCH | tms.carrier | carrier popup form create/update/delete | | /api/tms/driver-logs/{id}/deliveries | GET | tms.delivery | delivery rows attached to a driver log | | /api/tms/equipment-loads/{id}/deliveries | GET | tms.delivery | delivery rows attached to an equipment load | | /api/tms/equipment-loads/{id}/status | PATCH, POST | tms.delivery | office-only equipment-load status update for Export Dispatch Drayage Queue Mark Empty / Mark Full actions; STATUS=FULL requires a current-booking-list RETURN_BOOKING_ID for the same customer and updates the equipment return-booking link in the same command | | /api/tms/equipment-loads/{id}/return-booking-options | GET | tms.delivery | office-only return booking picker options for Export Dispatch Drayage Queue Mark Full, scoped to the current Export Dashboard booking list for the current container customer and promoting the current pickup booking when it is in that list | | /api/tms/delivery-orders | GET | tms.delivery | delivery-order rows by booking id | | /api/tms/companies/{id}/terminal-status | GET | tms.delivery | terminal flag check for delivery flows | | /api/tms/deliveries/{id}/status | POST | tms.delivery | update delivery runtime status | | /api/tms/deliveries/{id}/arrival-signal | POST | tms.delivery | driver near-yard / yard-entrance signal for pending arrival notification | | /api/tms/deliveries/{id}/arrival-action | PATCH | tms.delivery | office/operation action that clears pending arrival notification | | /api/tms/deliveries/{id}/location-ping | POST | tms.delivery | driver one-time current-location ping; creates DRIVER_LOCATION_PING operation event without background tracking | | /api/tms/deliveries/{id}/parking-request | POST | tms.delivery | assigned-driver export-empty parking request, with office override allowed; sets T_DELIVERY.PARKING_STATUS='PARKING_REQUESTED' and request timestamp | | /api/tms/deliveries/{id}/parking-assignment | POST, PATCH | tms.delivery | office-only parking assignment; accepts exact spots plus YMS area/stack-group/drop destinations, creates/updates the existing YMS yard queue for the delivery equipment, and stores assignment status/timestamp on T_DELIVERY | | /api/tms/deliveries/{id}/parking-confirm | POST, PATCH | tms.delivery | assigned-driver parking confirmation, with office override allowed; accepts optional ACTUAL_TO for area/stack-group parking assignments, confirms the active claimed YMS queue when present, and stores confirmation status/timestamp on T_DELIVERY | | /api/tms/deliveries/{id}/field | PATCH | tms.delivery | update allowed delivery operation fields without generic table input | | /api/tms/equipment/{id}/field | PATCH | tms.delivery | update allowed equipment fields from driver operation | | /api/tms/deliveries/{id}/driver-assignment | PATCH | tms.delivery | assign or cancel driver on a delivery | | /api/tms/dispatch/export-pickup | POST | tms.delivery | create export pickup dispatch | | /api/tms/dispatch/import-return | POST | tms.delivery | create import return dispatch | | /api/tms/dispatch/delivery | POST | tms.delivery | create delivery dispatch | | /api/tms/transloads/driver-contacted | PATCH | tms.transload | update driver contacted flag | | /api/tms/transloads/dispatch | POST | tms.transload | dispatch staged plan to docked execution | | /api/tms/transloads/cancel | POST | tms.transload | cancel active transload appointment/execution | | /api/tms/transloads/no-show | POST | tms.transload | mark scheduled/pre-planned/arrived transload as no-show | | /api/tms/transloads/scheduled-inquiry-email | POST | tms.transload | send scheduled-load inquiry emails grouped by customer for selected scheduled transload rows | | /api/tms/transload-emails/reschedule-decision | GET | tms.transload | validate requested transload reschedule slot against active rules | | /api/tms/transload-emails/approval-queue | GET | tms.transload | transload appointment email approval queue, including reviewed Shipper/Carrier/Equipment/Remark draft values; LIMIT is applied by email and all rows for included emails are returned; supports exact-link filters GMAIL_MESSAGE_ID, MAIL_MESSAGE_ID, and ITEM_ID | | /api/tms/transload-emails/outbound-queue | GET | tms.transload | transload email follow-up/outbound queue | | /api/tms/transload-emails/reschedule-apply | POST | tms.transload | apply approved reschedule email decision | | /api/tms/transload-emails/approval-submit | POST | tms.transload | approve/reject/delete appointment email queue item; unmatched CREATE preserves reviewed Carrier/Shipper/Equipment Type+Number/Remark when present and keeps legacy fallback when absent; legacy manual outcome remains available for integrations | | /api/tms/transload-emails/create-confirmation-reply | POST | tms.transload | send one same-thread create confirmation summary for multiple approved rows from one email | | /api/tms/transload-emails/reject-request | POST | tms.transload | reject open rows from one appointment email as one request, then send one same-thread rejection reply after commit | | /api/tms/transload-emails/rejection-reply | POST | tms.transload | send or retry one same-thread rejection reply for already rejected rows from one email | | /api/tms/transload-emails/item-process | POST | tms.transload | process parsed appointment email item |

For POST /api/tms/transload-emails/approval-submit, reviewed-field parity applies only to unmatched CREATE (ACTION_TYPE=CREATE, MATCHED_TRANSLOAD_ID=0). DRAFT.INITIAL_EQUIPMENT_TYPE accepts CONTAINER or TRAILER; a positive DRAFT.SHIPPER_COMPANY_ID must resolve inside the active tenant. Invalid values return 400 before appointment/order writes. Missing reviewed keys retain the legacy Carrier/Shipper/equipment/remark fallback, so this change does not add required fields. RESCHEDULE, CANCEL, matched CREATE, provider reply behavior, MK and schema are unchanged. Implementation status: COMPLETE / NOT_DEPLOYED. | /api/tms/transloads/{id}/load-label | GET | tms.transload | load-label print data | | /api/tms/transloads/{id}/load-label/warehouse-location | PATCH | tms.transload | update warehouse location from load-label print page | | /api/tms/transload-planner/events | GET | tms.transload | transload calendar/planner events | | /api/tms/transload-planner/events/{id} | PATCH | tms.transload | update transload planner appointment time | | /api/tms/loading-planner | POST | tms.transload | create dock/loading planner row without generic table write | | /api/tms/loading-planner/{id} | GET, PATCH, DELETE | tms.transload | read/update/delete dock/loading planner row with tenant ownership check; delete cancels related planner yard queues and returns active planning statuses to ARRIVED | | /api/tms/transload-loaders/options | GET | tms.transload | forklift loader option list for planning and assignment boards; replaces deleted legacy forklift-driver master lookup | | /api/tms/transloads/unloading-equipment/options | GET | tms.transload | bounded unloading-equipment autocomplete options; replaces deleted legacy unloading-equipment autocomplete flow | | /api/tms/staff-time/current | GET | tms.transload | current staff clock state for inbound-loader and staff time controls, including the current open break/lunch state when a BREAK_IN has no paired BREAK_OUT; DAY_SECONDS and WEEK_SECONDS include paid 10MIN BREAK time and exclude LUNCH BREAK time | | /api/tms/staff-time/event | POST | tms.transload | create self-service staff time event; active self-service events are clock in/out only | | /api/tms/staff-time/timeline | GET | tms.transload | staff-time event timeline read model for /loader-timeline; returns an empty list if staff-time storage has not been deployed yet | | /api/tms/staff-time/current-context | PATCH | tms.transload | update the current open clock-in WORK_CONTEXT for a staff member so planning auto-assignment can exclude clocked-in staff doing non-forklift work; managers may update other staff and users may update their own context | | /api/tms/staff-time/manager-events | POST | tms.transload | manager-only clock or break/lunch correction create with required reason; break/lunch creates BREAK_IN and auto-creates BREAK_OUT after its duration elapses | | /api/tms/staff-time/manager-events/{id} | PATCH, DELETE | tms.transload | manager-only clock or break/lunch correction update/delete with required reason | | /api/tms/transload-appointment-blocks | GET, POST, DELETE | tms.transload | list/create/remove appointment time blocks | | /api/tms/transload-timeblocks/status | GET | tms.transload | appointment timeblock occupied/reserved/blocked status with scheduled load display context | | /api/tms/transload-timeblocks/options | GET | tms.transload | transload appointment timeblock options; replaces generic master-data lookup for TRANSLOAD_TIMEBLOCK | | /api/oms/commercial-orders/options | GET | oms.commercial-order | commercial order editor options including company templates | | /api/oms/commercial-orders | GET, POST | oms.commercial-order | commercial order list/create | | /api/oms/commercial-orders/{id} | GET, PATCH, DELETE | oms.commercial-order | commercial order detail/update/delete | | /api/oms/inbound-orders/options | GET | oms.inbound-order | inbound purchase order editor options | | /api/oms/inbound-orders/commodity-rate-options | GET | oms.inbound-order | commodity/rate editor options with companies, commodities, and UOMs | | /api/oms/inbound-orders/doortrade-packing-list/ai-extract | POST | oms.inbound-order | Gemini-backed DOORTRADE_PACKING_LIST extraction for inbound order line import | | /api/oms/inbound-orders | GET, POST | oms.inbound-order | inbound purchase order list/create | | /api/oms/inbound-orders/schedule | GET | oms.inbound-order | inbound purchase order schedule events | | /api/oms/inbound-orders/calendar-events | GET | oms.inbound-order | inbound purchase order calendar events | | /api/oms/inbound-orders/{id} | GET, PATCH, DELETE | oms.inbound-order | inbound purchase order detail/update/delete | | /api/oms/inbound-orders/{id}/atomic-save | POST | oms.inbound-order | editor-only atomic header/item/delete Save | | /api/oms/inbound-orders/{id}/complete-without-scale | POST | oms.inbound-order | complete inbound purchase without scale execution | | /api/oms/inbound-orders/{id}/items | POST | oms.inbound-order | add inbound purchase order item | | /api/oms/inbound-orders/{id}/items/{itemId} | PATCH, DELETE | oms.inbound-order | update/delete inbound purchase order item | | /api/oms/customer-pos/by-load-numbers | POST | oms.inbound-order | customer PO lookup by load numbers | | /api/oms/customer-pos | POST | oms.inbound-order | customer PO create with item rows | | /api/oms/customer-pos/form | POST, PATCH | oms.inbound-order | customer PO popup form create/update/delete | | /api/oms/customer-pos/{id} | GET | oms.inbound-order | customer PO row detail for popup edit flows | | /api/oms/customer-pos/{id}/items | GET | oms.inbound-order | customer PO item rows with linked transload status | | /api/oms/outbound-orders/options | GET | oms.outbound-order | outbound sales order editor options | | /api/oms/outbound-orders | GET, POST | oms.outbound-order | outbound sales order list/create | | /api/oms/outbound-orders/schedule | GET | oms.outbound-order | outbound sales order schedule events | | /api/oms/outbound-orders/{id} | GET, PATCH, DELETE | oms.outbound-order | outbound sales order detail/update/delete | | /api/oms/outbound-orders/{id}/items | POST | oms.outbound-order | add outbound sales order item | | /api/oms/outbound-orders/{id}/items/{itemId} | PATCH, DELETE | oms.outbound-order | update/delete outbound sales order item | | /api/oms/outbound-orders/{id}/costs | GET, POST | oms.outbound-order | outbound order cost detail/upsert | | /api/oms/outbound-orders/{id}/costs/{transactionId} | DELETE | oms.outbound-order | delete outbound order cost transaction | | /api/oms/export-brokerage/bookings/search | GET | oms.outbound-order + oms.inbound-order | bounded booking autocomplete for export brokerage workflow-selected bookings | | /api/oms/export-brokerage/documents/ai-extract | POST | oms.outbound-order + oms.inbound-order | Gemini-backed EXPORT_BROKERAGE_DOCUMENT extraction for export brokerage job setup | | /api/oms/export-brokerage/jobs | POST | oms.outbound-order + oms.inbound-order | one-step export brokerage job create; creates inbound purchase, inventory lots/transload context, outbound sales order, and outbound allocation without scale execution | | /api/oms/export-brokerage/jobs/cancel | POST | oms.outbound-order + oms.inbound-order | cancel a one-step export brokerage job by INBOUND_ORDER_ID and OUTBOUND_ORDER_IDS; deletes the created inbound/outbound orders when no downstream scale, delivery, transload, ledger, or outside allocation activity exists | | /api/oms/allocations/workbench | GET | oms.allocation | allocation workbench read model | | /api/oms/allocations/target-shipments | GET | oms.allocation | target outbound shipment autocomplete | | /api/oms/allocations/allocatable-lots/search | GET | oms.allocation | bounded allocatable inventory lot autocomplete for outbound allocation source selection | | /api/oms/allocations/apply | POST | oms.allocation | apply staged inventory lots to outbound demand | | /api/oms/trade-summary | GET | oms.trade-summary | trade summary list/read model | | /api/oms/trade-summary/{equipmentId} | GET | oms.trade-summary | trade summary detail read model | | /api/oms/deal-ledger | GET | oms.trade-summary | deal ledger alias for trade summary list | | /api/oms/deal-ledger/{equipmentId} | GET | oms.trade-summary | deal ledger alias for trade summary detail | | /api/wms/inbound/transloads/check-in-list | GET | wms.inbound | WMS-owned inbound check-in list | | /api/wms/inbound/transloads/completed | GET | wms.inbound | WMS-owned completed inbound transload list | | /api/wms/inbound/imports/transload | POST | wms.inbound | create transload from import equipment | | /api/wms/inbound/imports/delivery-orders | POST | wms.inbound | create import delivery order group | | /api/wms/inbound/import-delivery-orders/ai-extract | POST | wms.inbound | Gemini-backed IMPORT_DELIVERY_ORDER extraction through the WMS inbound owner route | | /api/wms/inbound/import-delivery-orders/ai-prompt | GET, PATCH | wms.inbound | read/save the tenant prompt for IMPORT_DELIVERY_ORDER extraction | | /api/wms/inventory/lots | GET | wms.inventory | inventory lot list | | /api/wms/inventory/lots/{id} | GET, PATCH | wms.inventory | inventory lot detail/update | | /api/wms/inventory/lots/{id}/adjust | POST | wms.inventory | manual inventory lot remaining-balance adjustment | | /api/wms/inventory/warehouse-map/state | GET | wms.inventory | canonical read-only Warehouse Map grid, grid-owned cellType, and storage-cell inventory-lot occupancy model | | /api/wms/inventory/warehouse-map/locations | GET | wms.inventory | active warehouse location master rows for shared callers | | /api/wms/inventory/warehouse-map/location-options | GET | wms.inventory | active, unblocked warehouse location options for Loader/Inbound callers, including Firelane exceptions | | /api/wms/inventory/report | GET | wms.inventory | inventory report read model | | /api/wms/inventory/physical-report | GET | wms.inventory | Inventory Report Physical tab read model; returns previous finalized inventory lot snapshot and current saved or generated draft | | /api/wms/inventory/physical-report/draft | POST | wms.inventory | save editable inventory lot snapshot rows for the Physical tab by company + period | | /api/wms/inventory/physical-report/finalize | POST | wms.inventory | finalize the inventory lot snapshot as customer-reported for future previous/email reuse | | /api/wms/storage/by-company | GET | wms.inventory | storage by company report | | /api/wms/inventory/bale-adjustments | GET | wms.inventory | computed bale adjustment queue from completed transloads and inventory lot ledger state | | /api/wms/inventory/bale-adjustments/apply | POST | wms.inventory | apply exact extra sources, atomically create-and-apply a manual extra source, apply source lots to created leftover loads, create leftover, or create/mark spare source lots with ledger rows | | /api/wms/inventory/bale-reviews | GET | wms.inventory | Final Loading Review queue with server-derived Expected/Final/Difference, readiness and BAL TRK summary | | /api/wms/inventory/bale-reviews/confirm | POST | wms.inventory | final confirm after source, leftover, and weight-review checks are satisfied | | /api/wms/inventory/bale-reviews/apply-weight | POST | wms.inventory | ACTION=USE_INBOUND_WEIGHT; applies inbound final weight to the final weight using a stale-value guard | | /api/wms/inventory/bale-reviews/reopen | POST | wms.inventory | reopens review status only; inventory ledger and lot balances remain unchanged | | /api/wms/inventory/bale-sources/options | GET | wms.inventory | exact Spare-source autocomplete for an Extra adjustment target; returns load/reference, customer, commodity, location, and remaining balance | | /api/wms/inventory/bale-sources | GET | wms.inventory | bale source pool read model from T_INVENTORY_LOT | | /api/wms/inventory/bale-sources/manual | POST | wms.inventory | create manual source lot and LOT_CREATED ledger row | | /api/wms/inventory/bale-sources/{lotUuid}/ledger | GET | wms.inventory | source ledger drawer data for a lot UUID | | /api/wms/inventory/bale-sources/{lotUuid}/count-check | POST | wms.inventory | update remaining count/weight and insert COUNT_CHECK; CTC use requires Office, an active placed Spare lot, a reason, and a quantity not below reservation | | /api/wms/inventory/bale-sources/{lotUuid}/void | POST | wms.inventory | insert REVERSAL ledger row and restore lot balance | | /api/files | GET, POST, PATCH | none | shared file metadata lookup, registration, and bulk type/delete updates | | /api/files/{id} | PATCH, DELETE | none | shared file metadata type/sequence update and delete with CLIENT_ID ownership check | | /api/files/transaction-documents | POST | none | register uploaded AP transaction documents against multiple transactions | | /api/files/media/upload | POST | none | physical media upload with hybrid EFS/S3 storage behavior | | /api/files/media/download | POST | none | zip/download response for selected stored files | | /api/files/media/datestamp | POST | none | add equipment-load photo datestamp image and register T_FILE metadata | | /api/bms/scale-tickets | POST | bms.scale-ticket | create scale ticket with tenant ticket-number generation | | /api/bms/scale-tickets/{id} | GET, PATCH, DELETE | bms.scale-ticket | scale ticket detail/update/delete | | /api/bms/scale-tickets/closing | POST | bms.scale-ticket | create/update a closing scale ticket after validating the equipment-load/transload/commodity/ticket relation; axle values, times, and tractor number are client-authored, while load/customer/driver/commodity metadata is derived from the validated equipment/transload relation | | /api/bms/scale-tickets/outbound-weight-apply | POST | bms.scale-ticket | apply outbound weight from scale ticket | | /api/bms/transactions/{id} | GET | bms.transaction | transaction popup detail with transaction list rows | | /api/bms/transactions/form | POST, PATCH | bms.transaction | transaction popup form create/update/delete | | /api/bms/transactions/{source} | POST, DELETE | bms.transaction | create/delete source-owned trucking, transload, chassis, or scale-ticket transactions | | /api/bms/transactions | DELETE | bms.transaction | delete selected transaction rows by transaction id | | /api/bms/transactions/close | PATCH | bms.transaction | close selected transactions | | /api/bms/transactions/chassis/bulk-input | POST | bms.transaction | create chassis transactions from bulk pasted container/rate input | | /api/bms/transactions/apply-final-transload | POST | bms.transaction | apply final transload weight/quantity to equipment commodity rows | | /api/bms/companies/{id}/billing-address | GET | bms.transaction | transaction popup billing address lookup | | /api/bms/ar-billing/options | GET | bms.transaction | AR Billing companies, transaction types, presets, and QBO readiness | | /api/bms/ar-billing/presets | GET, POST | bms.transaction | list/create AR Billing preview presets | | /api/bms/ar-billing/presets/{uuid} | PATCH, DELETE | bms.transaction | update/delete AR Billing preview preset by UUID | | /api/bms/ar-billing/rates | GET | bms.transaction | customer registered trucking/transload/chassis rate structures and prices for Billing Presets actual-rate review | | /api/bms/ar-billing/rates/form | PATCH | bms.transaction | update the final trucking/transload/chassis price row surfaced in the Rate customer modal | | /api/bms/ar-billing/workbench | GET | bms.transaction | paged booking/container AR Billing workbench rows | | /api/bms/ar-billing/preview | POST | bms.transaction | generate editable charge-line preview without writing transactions | | /api/bms/ar-billing/confirm | POST | bms.transaction | create/update container-scoped AR transactions from confirmed preview lines | | /api/bms/ar-billing/qbo-sync | POST | bms.transaction | create QuickBooks Online invoices for confirmed container AR transactions | | /api/bms/fuel/{id} | GET | bms.transaction | fuel row detail for popup edit fallback | | /api/bms/fuel/form | POST, PATCH | bms.transaction | fuel popup form create/update/delete | | /api/reports/inventory/monthly | GET | addon.reports | monthly inventory report read model | | /api/reports/inventory/accumulative | GET | addon.reports | accumulative inventory report read model | | /api/reports/mk/daily-commodity | GET | addon.reports | MK daily commodity report read model | | /api/bms/reports/account-trucking | GET | addon.reports | account trucking report read model used by BMS table flows | | /api/reports/fbol | GET | addon.reports | FBOL report read model | | /api/addons/ai-verifications/{id} | GET | addon.reports | AI verification report lookup by delivery id |

Request Shapes

Conditional React API client

If P12 selects React, its client proposal must be re-reviewed against the current framework-independent API contract before implementation.

Example calls:

api.yms.yardMap.getState();
api.yms.yardMoves.preview(input);
api.tms.transloads.checkIn(id, input);
api.wms.inventory.listLots(query);
api.oms.outboundOrders.update(id, input);

The client contract does not accept TYPE, ACTION, or USERNAME.

Shared read/write

The shared query.php / post.php request shape was removed in #454. Do not add new callers or compatibility routes for this shape.

Direct module read

Legacy module-owned shell only; do not use for new React/API v2 callers.

GET /app/systems/<SYSTEM>/<module>/query.php
TYPE=<domain_type>
ACTION=<action_name>
DATA=<payload>
USERNAME=<optional>

Direct module write

Legacy module-owned shell only; do not use for new React/API v2 callers.

POST /app/systems/<SYSTEM>/<module>/post.php
TYPE=<domain_type>
ACTION=<action_name>
DATA=<payload>
USERNAME=<optional>

Response Envelope

Success

{
  "Result": "SUCCESS",
  "Value": {},
  "Time": "YYYY-MM-DD HH:mm:ss",
  "Debug": "",
  "Query": "",
  "ReqId": "..."
}

Error

{
  "Result": "ERROR",
  "Message": "visible error message",
  "Value": "",
  "Time": "YYYY-MM-DD HH:mm:ss",
  "Debug": "",
  "ReqId": "..."
}

Auth And Module Gate

  • Auth helper: endpointAssertAuthenticated().
  • API v2 auth helper: apiAssertAuthenticated().
  • Page/module guard: app/platform/tenant/module_guard.php.
  • TYPE/ACTION module key resolver: moduleGuardEndpointModuleKey().
  • API v2 module key source: route moduleKey, not TYPE/ACTION.
  • Disabled module endpoint response: HTTP 403 + JSON error envelope.
  • Super-admin impersonation checks the target tenant, not raw source session CLIENT_ID.

Active Direct Endpoint Shells

Module Read Write
oms.commercial-order OMS/commercial-order/query.php OMS/commercial-order/post.php
oms.outbound-order OMS/outbound-order/query.php OMS/outbound-order/post.php
oms.allocation OMS/allocation/query.php OMS/allocation/post.php
wms.inbound WMS/inbound/query.php WMS/inbound/post.php
wms.inventory WMS/inventory/query.php WMS/inventory/post.php
tms.transload TMS/transload/query.php none
yms.yard-map YMS/yard-map/query.php none
yms.yard-move YMS/yard-move/query.php YMS/yard-move/post.php
yms.container-tracking YMS/container-tracking/query.php none
yms.dock-scheduling YMS/dock-scheduling/query.php none
yms.scale YMS/scale/query.php YMS/scale/post.php
bms.rate BMS/rate/query.php BMS/rate/post.php
bms.scale-ticket none BMS/scale-ticket/post.php
bms.invoice BMS/invoice/query.php none

Key TYPE Families

Family Owner
MENU platform.menu; active menu/admin callers use /api/platform/menu/*
ACCOUNT platform.auth; active password caller uses /api/platform/account/password
SUPER_ADMIN platform.audit; active admin-console callers use /api/platform/admin/*; tenant onboarding uses POST /api/platform/admin/clients with ONBOARDING_ENABLED=1 or POST /api/platform/admin/tenants/{clientId}/onboarding
TRANSLOAD, TRANSLOAD_PLANNER, TRANSLOAD_TIMEBLOCK_STATUS tms.transload
TRANSLOAD_APPOINTMENT_BLOCK tms.transload
TRANSLOAD_EMAIL_* tms.transload, email add-on bridge
MOBILE_DRIVER, MOBILE_CONTAINER, delivery lists tms.delivery; driver-operation callers now use API v2 delivery routes
YARD_LOCATION, YARD_DISPATCH_QUEUE, RESET_YARD yms.yard-move
YARD_MAP, YARD_OCCUPANCY yms.yard-map
YMS_SCALE yms.scale
SCALE_TICKET, CLOSING_SCALE_TICKET, OUTBOUND_WEIGHT_APPLY bms.scale-ticket
MK_COMMODITY_RATE, MK_TRUCKING_RATE, MK_FREIGHT_RATE bms.rate
MK_PURCHASE_ORDER oms.inbound-order
MK_SALES_ORDER oms.outbound-order
MK_ALLOCATION_* oms.allocation
MK_INVENTORY, MK_INVENTORY_LOT wms.inventory
MK_BOOKING, BOOKING_* tms.booking
COMMERCIAL_ORDER oms.commercial-order
TRADE_SUMMARY, DEAL_LEDGER oms.trade-summary
FILE shared file helper; active metadata callers use /api/files*
report add-ons addon.reports; active inventory/MK/account-trucking/FBOL/AI-verification report callers use /api/reports*, /api/bms/reports/account-trucking, or /api/addons/ai-verifications/*; INGATE_TICKET extraction uses /api/addons/ai-verifications/ingate-ticket/ai-extract
TELEGRAM addon.telegram; active shared send callers use POST /api/addons/telegram/messages

Tenant Onboarding

  • Super-admin tenant create can run onboarding in the same transaction by posting ONBOARDING_ENABLED=1 to POST /api/platform/admin/clients.
  • Super-admin can also apply onboarding defaults to an explicitly selected tenant with POST /api/platform/admin/tenants/{clientId}/onboarding.
  • Onboarding seeds default T_ROLE rows, an initial admin T_USER, enabled T_TENANT_MODULE rows by package tier, required T_TENANT_CONFIG defaults, and a baseline T_MENU tree.
  • Valid package tiers are starter, professional, and enterprise.
  • Current schema names are T_MODULE.MODULE_KEY, T_TENANT_MODULE.IS_ENABLED, and T_TENANT_CONFIG.CONFIG_KEY / CONFIG_VALUE.
  • Production tenant onboarding writes require explicit user confirmation. Local write verification must follow the DB target confirmation in .agents/rules/DEV_ENV.md.

Important Contracts

File metadata

  • Physical uploads use POST /api/files/media/upload; the legacy app/controllers/media.php ACTION=UPLOAD controller remains compatibility-only.
  • Zip downloads use POST /api/files/media/download.
  • Equipment-load photo datestamps use POST /api/files/media/datestamp.
  • File metadata registration uses POST /api/files; request bodies use FILE_TYPE, not legacy TYPE.
  • File URL lookup uses GET /api/files?TABLE_NAME=...&TABLE_ID=....
  • Multi-transaction AP document registration uses POST /api/files/transaction-documents with TABLE_NAME=T_TRANSACTION, TABLE_IDS=[...], FILE_TYPE=AP_INVOICE|AP_PAYMENT, and FILES=[...].
  • Transaction document registration stores one T_FILE metadata row per transaction and file so a shared Pierpass/CTF payment document can be found from every paid transaction.
  • File metadata update/delete routes verify T_FILE.CLIENT_ID ownership before changing or deleting rows.

Commercial order

  • Active API v2 routes are GET/POST /api/oms/commercial-orders, GET/PATCH/DELETE /api/oms/commercial-orders/{id}, and GET /api/oms/commercial-orders/options.
  • Legacy read options remain available during cutover as TYPE=COMMERCIAL_ORDER ACTION=OPTIONS.
  • OPTIONS returns COMPANY_TEMPLATES keyed by T_COMPANY.ID when tenant config COMMERCIAL_ORDER_COMPANY_TEMPLATES is present.
  • Template fields are optional and prefill-only; create/update payloads still persist explicit form values.
  • Supported template keys include existing T_COMMERCIAL_ORDER fields plus ORDER_NUMBER_PATTERN; signature presets may use SELLER_SIGNATURE and BUYER_SIGNATURE.
  • SHIPPING_PERIOD is the user-facing Due Date; create/update payloads store date-only YYYY-MM-DD values.
  • ORDER_NUMBER_PATTERN supports [yyyymmdd], [yymmdd], [yyyy], [mm], [dd], and [material].
  • Current Commercial Order mutations run in one DB transaction. Create requires a same-tenant counterparty and at least one positive-quantity valid-UOM line; an unchanged preview number is reserved under a tenant/type/date lock.
  • The current UI stores an optional Purchase-to-Sales relation from the Purchase editor. The backend accepts only a same-tenant, non-self, opposite-type active row but does not enforce status or cardinality.
  • The current implementation classifies pre-execution by active T_LOAD.COMMERCIAL_ORDER_ID count: count zero lets PATCH replace the complete item set; positive count skips item replacement and rejects type change and DELETE while other submitted header fields remain editable. T_LOAD_ITEM.COMMERCIAL_ORDER_ITEM_ID-only execution is not covered by that guard and must not be inferred safe from an empty Detail execution list.
  • Commercial STATUS accepts DRAFT, OPEN, PARTIAL, FULFILLED, and CANCELLED; these are not automatically derived from execution state.
  • The shared legacy-compatible body adapter currently uppercases non-signature scalar values recursively. Callers must verify exact detail read-back; changing this compatibility behavior requires a separate scoped packet.

OMS purchase/sales orders

  • Canonical operator behavior and recovery are documented separately in docs/processes/MK_INBOUND_PURCHASE_WORKFLOW.md and docs/processes/MK_OUTBOUND_SALES_WORKFLOW.md; API route existence does not imply a unified completion state or permission to mutate progressed data.

  • Inbound purchase order API v2 routes are GET/POST /api/oms/inbound-orders, GET/PATCH/DELETE /api/oms/inbound-orders/{id}, POST /api/oms/inbound-orders/{id}/atomic-save, POST /api/oms/inbound-orders/{id}/complete-without-scale, POST /api/oms/inbound-orders/{id}/items, PATCH/DELETE /api/oms/inbound-orders/{id}/items/{itemId}, GET /api/oms/inbound-orders/options, GET /api/oms/inbound-orders/commodity-rate-options, GET /api/oms/inbound-orders/schedule, GET /api/oms/inbound-orders/calendar-events, and POST /api/oms/inbound-orders/doortrade-packing-list/ai-extract.

  • POST /api/oms/inbound-orders/{id}/atomic-save is used only by the shared inbound editor's existing-order Save. Its body is { HEADER, ITEMS, DELETED_ITEM_IDS }; the server validates that every existing/deleted item belongs to the same tenant/order, applies header, deletes, item updates/inserts, and final state synchronization in one transaction. Any rejected child rolls back the complete request. Existing header/item endpoints remain available for their current callers.
  • Outbound sales order API v2 routes are GET/POST /api/oms/outbound-orders, GET/PATCH/DELETE /api/oms/outbound-orders/{id}, POST /api/oms/outbound-orders/{id}/items, PATCH/DELETE /api/oms/outbound-orders/{id}/items/{itemId}, GET /api/oms/outbound-orders/options, and GET /api/oms/outbound-orders/schedule.
  • Export brokerage booking autocomplete is GET /api/oms/export-brokerage/bookings/search; it returns bounded existing booking suggestions under the same oms.outbound-order plus oms.inbound-order and export brokerage workflow gates as the page. Export brokerage document extraction is POST /api/oms/export-brokerage/documents/ai-extract; document booking numbers are returned as DOCUMENT_BOOKING_NUMBER hints only. One-step create is POST /api/oms/export-brokerage/jobs; ocean-freight payloads must send a selected existing BOOKING_ID and must not rely on PDF-extracted booking text to create a booking. Export brokerage create uses one job-level time for IN_TIME, OUT_TIME, and INGATE_TIME; commodity line weight is normalized as GROSS_WEIGHT = NET_WEIGHT with TARE_WEIGHT = 0; rates must be positive; and rates and UOM must be identical for the same COMMODITY_ID across containers. These routes require oms.outbound-order plus oms.inbound-order and the export brokerage workflow tenant config. Create reuses the inbound/outbound OMS write helpers in one API transaction and does not create or require a scale ticket. Cancel is POST /api/oms/export-brokerage/jobs/cancel with INBOUND_ORDER_ID and OUTBOUND_ORDER_IDS; it rejects orders with downstream scale, delivery, transload, inventory ledger, cost, or outside allocation activity.
  • Inbound purchase and outbound sales create/header update bodies may omit COMMERCIAL_ORDER_ID; blank or non-positive values are stored as -1 until a commercial PO/SO is available. Positive inbound values must validate as ORDER_TYPE='PURCHASE', and positive outbound values must validate as ORDER_TYPE='SALES'. POST /api/yms/scale/purchase-inbound/walk-in also creates PO-less walk-in shells, but it must ignore generic order-create fields and server-normalize the shell to a LOCAL/OPEN walk-in with scheduled items. List/detail responses include COMMERCIAL_ORDER_NUMBER and COMMERCIAL_ORDER_REFERENCE_NUMBER for PO/SO display.
  • Inbound purchase create/header update bodies may include OPERATIONAL_STATUS (SCHEDULED, UNLOADED, TRANSLOADED; MIXED is response-only/no bulk update) and APPLY_OPERATIONAL_STATUS=1 to apply the status to purchase items. When a non-empty OUT_TIME is newly entered without a meaningful status override, the write path treats the order as UNLOADED; either path can synchronize the transload and materialize an inventory lot outside explicit Scale/No-scale finalize. This is current behavior with an unapproved business-policy boundary, not an implicit completion recommendation.
  • POST /api/oms/inbound-orders/{id}/complete-without-scale accepts OUT_TIME and EXECUTION_BRANCH (WAREHOUSE_ONLY, DIRECT_OUTBOUND, MIXED) for brokerage/no-scale purchase completion. It only accepts scheduled inbound purchase orders with ORDER_TYPE='OCEAN_FREIGHT', rejects cancelled, progressed/completed, and scale check-in-linked orders, treats existing purchase item input as the actual basis, sets purchase items to UNLOADED, syncs T_INVENTORY_LOT, and updates the purchase transload without creating a scale ticket.
  • The three inbound execution-branch values produce the same durable received order/item/transload/lot state. Direct/Mixed only return an Allocation Workbench URL; the selection is not persisted and does not create an allocation.
  • Current source-transload inventory materialization requires exactly one active purchase item. Scale finalize can calculate multi-line weight parts, but the following exact transload-to-lot sync rejects more than one item and rolls the request back; multi-line receipt completion is not a supported/proven contract.
  • Normal inbound create uses submitted NET_WEIGHT, falling back to QUANTITY when omitted; only physical scale finalize redistributes measured gross/tare/net across lines. Callers must not assume a universal backend Gross-minus-Tare derivation.
  • Direct outbound create/add-item remains UNMAPPED and does not write T_LOAD_ALLOCATION or reduce a lot. Outbound OPERATIONAL_STATUS, READINESS, and Schedule rows are derived read projections, not completion commands.
  • The current Outbound editor sends Ocean create/update compatibility flags that allow a missing Booking and an unbooked or absent equipment bridge. An Ocean header can therefore save without Booking/Container/Size; this is an incomplete current state, not a server-enforced required-field contract.
  • Positive Outbound Booking and party IDs do not have complete same-tenant active-row validation in the header write path; commodity ID/name direct-input validation also requires negative fixtures. Do not treat a successful header write as tenant-reference integrity evidence.
  • Outbound create/update synchronizes the optional Booking reverse reference plus equipment/container, equipment-load, commodity-summary, and scale-ticket bridges. Allocation, Booking lifecycle, Packing, Scale execution, Delivery, and Cost remain separately owned effects.
  • Outbound cost API v2 routes are GET/POST /api/oms/outbound-orders/{id}/costs and DELETE /api/oms/outbound-orders/{id}/costs/{transactionId}.
  • Shared MK inbound/outbound order editors use domain API v2 routes for order options, detail, header writes, item writes, outbound cost writes, and commodity-rate options. File, delivery, and transload helper calls remain with their owning tasks until migrated.
  • Direct inbound/outbound order creates with blank ORDER_NUMBER reserve tenant/date-scoped numbers as POYYYYMMDD-### and SOYYYYMMDD-###.

Allocation workbench

  • API v2 routes are GET /api/oms/allocations/workbench, GET /api/oms/allocations/target-shipments, GET /api/oms/allocations/allocatable-lots/search, and POST /api/oms/allocations/apply.
  • POST /api/oms/allocations/apply is the only allocation apply write route; callers must not use MK_SALES_ORDER ACTION=APPLY_WORKBENCH.
  • Target shipments must have an active execution container with CONTAINER_NUMBER and SIZE; workbench reads must not surface no-container targets, and apply writes must reject them.
  • Lot rows expose inventory visibility fields such as STOCK_BUCKET, STOCK_SOURCE_LABEL, SOURCE_ITEM_STATUS, IS_STALE_PARTIAL, AGE_DAYS, and VISIBILITY_NOTES.
  • Warehouse inventory summary rows expose lot counts, partial/stale partial counts, received date range, and source item statuses.
  • Apply writes T_LOAD_ALLOCATION through the OMS allocation handler and must remain inside a transaction with sync task flushing.

Trade summary / deal ledger

  • oms.trade-summary owns the trade summary and deal ledger read models, even though the current page lives under frontend/pages/system/BMS/reports/.
  • API v2 routes are GET /api/oms/trade-summary, GET /api/oms/trade-summary/{equipmentId}, GET /api/oms/deal-ledger, and GET /api/oms/deal-ledger/{equipmentId}.
  • These routes are read-only and must preserve the trade-summary response contract. Implementations may use the optimized base-table read model plus targeted T_TRANSACTION/T_TRANSACTION_LIST AP/AR/cost totals instead of materializing V_TRADE_SUMMARY.

WMS inbound / inventory

  • WMS inbound API v2 routes are GET /api/wms/inbound/transloads/check-in-list, GET /api/wms/inbound/transloads/completed, POST /api/wms/inbound/imports/transload, POST /api/wms/inbound/imports/delivery-orders, POST /api/wms/inbound/import-delivery-orders/ai-extract, and GET/PATCH /api/wms/inbound/import-delivery-orders/ai-prompt.
  • IMPORT_DELIVERY_ORDER AI extraction and prompt editing are WMS inbound-owned; callers should use KJ.api.ai() / KJ.api.aiPrompt() so the central client can route to the owner endpoint.
  • WMS inventory API v2 routes are GET /api/wms/inventory/lots, GET/PATCH /api/wms/inventory/lots/{id}, POST /api/wms/inventory/lots/{id}/adjust, GET /api/wms/inventory/reload-source-lots, GET /api/wms/inventory/warehouse-map/state, GET /api/wms/inventory/warehouse-map/locations, GET /api/wms/inventory/warehouse-map/location-options, GET /api/wms/inventory/report, GET /api/wms/inventory/physical-report, POST /api/wms/inventory/physical-report/draft, POST /api/wms/inventory/physical-report/finalize, and GET /api/wms/storage/by-company.
  • Bale adjustment API v2 routes are GET /api/wms/inventory/bale-adjustments, POST /api/wms/inventory/bale-adjustments/apply, GET /api/wms/inventory/bale-reviews, POST /api/wms/inventory/bale-reviews/confirm, POST /api/wms/inventory/bale-reviews/apply-weight, POST /api/wms/inventory/bale-reviews/reopen, GET /api/wms/inventory/bale-sources/options, GET /api/wms/inventory/bale-sources, POST /api/wms/inventory/bale-sources/manual, GET /api/wms/inventory/bale-sources/{lotUuid}/ledger, POST /api/wms/inventory/bale-sources/{lotUuid}/count-check, and POST /api/wms/inventory/bale-sources/{lotUuid}/void.
  • Inventory lot writes preserve the existing transaction path and update T_INVENTORY_LOT through the WMS inventory handler.
  • T_INVENTORY_LOT is the WMS inventory source of truth; tenant-scoped reads and writes must include CLIENT_ID.
  • Bale adjustment responses expose UUIDs through explicit string aliases (LOT_UUID, LEDGER_UUID, SOURCE_TRANSLOAD_UUID, RELATED_TRANSLOAD_UUID, RELATED_ALLOCATION_UUID) and must not expose raw binary UUID.
  • Bale adjustment queue state is computed from completed T_TRANSLOAD rows, final count/weight readiness, source balance, and T_INVENTORY_LOT_LEDGER; v1 has no persistent adjustment queue table.
  • Bale adjustment write routes run inside transactions. Extra applies decrement source lot remaining balance and insert EXTRA_APPLIED; reload completion decrements the selected active WMS source lot and inserts RELOAD_APPLIED; created-leftover-load apply decrements selected source lots and inserts LEFTOVER_LOAD_APPLIED; leftover/spare/manual source creation inserts or marks T_INVENTORY_LOT and inserts a creation ledger row; count check inserts COUNT_CHECK; void never deletes original history and instead inserts REVERSAL. Warehouse Map itself is read-only after Task #1339.
  • Bale adjustment writes reject missing required UUID aliases, already-applied transload adjustments, non-spare SPARE_CREATE requests, and extra/leftover/spare source quantities or weights that do not match the completed transload variance.

BMS rate / scale-ticket / invoice / transaction

  • BMS rate API v2 routes are GET /api/bms/rates/{commodities|trucking|freight}/options, GET/POST /api/bms/rates/{commodities|trucking|freight}, PATCH/DELETE /api/bms/rates/{commodities|trucking|freight}/{id}, GET /api/bms/rates/form-row?table=<table>&id=<id> for legacy popup edit hydration, and POST/PATCH /api/bms/rates/form for legacy popup rate-table creates/updates/deletes.
  • Rate lookup dependencies use GET /api/bms/rates/trucking/lookup, GET /api/bms/rates/chassis/by-equipment-load, and GET /api/bms/rates/chassis/by-company.
  • BMS scale-ticket API v2 routes are GET /api/bms/scale-tickets/history?TICKET_NUMBER=TYYYYMMDD-NNNN for the tenant-scoped, date-bounded axle change history; POST /api/bms/scale-tickets; GET/PATCH/DELETE /api/bms/scale-tickets/{id}; POST /api/bms/scale-tickets/closing; and POST /api/bms/scale-tickets/outbound-weight-apply. The history route returns only scale-ticket axle/timestamp fields from successful same-day JSONL create/update records, never the raw log payload.
  • BMS invoice read API v2 routes are GET /api/bms/invoices/local-purchase and GET /api/bms/transaction-documents/{id}. Print pages may keep server-rendered PHP helpers, but client/API contracts must not reintroduce BMS_LOCAL_* legacy requests.
  • BMS transaction API v2 routes are GET /api/bms/transactions/{id}, POST/PATCH /api/bms/transactions/form, POST/DELETE /api/bms/transactions/{trucking|transload|chassis|scale-ticket}, DELETE /api/bms/transactions, PATCH /api/bms/transactions/close, POST /api/bms/transactions/chassis/bulk-input, and POST /api/bms/transactions/apply-final-transload.
  • AR Billing API v2 routes under /api/bms/ar-billing/* use UUID request/response identifiers for presets, bookings, containers, transaction rows, and transaction lines. GET /api/bms/ar-billing/workbench accepts DATE_FROM, DATE_TO, BILLING_COMPANY_UUID, BOOKING_NUMBER, CONTAINER_NUMBER, STATUS, and the kj-table paging parameters jtStartIndex/jtPageSize, while legacy KEYWORD remains a combined booking/container fallback. The response returns paged items plus total, page, pageSize, and startIndex, and each row includes booking, equipment, customer, existing source AR amount or suggested AR amount, invoice/date/check/payment/status/QBO, and charge-source context for the AR Billing container table. Preview routes must not create T_TRANSACTION; confirm must reject containers that already have source AR transactions or charges for multiple billing customers, and confirmed container invoices write one T_TRANSACTION header plus multiple T_TRANSACTION_LIST rows.
  • Exact legacy MK_BILLING_PURCHASE_LIST, MK_BILLING_SALES_LIST, and MK_BILLING_REPORT callers/handlers were not found in the 2026-06-27 active scan outside the module guard prefix. Do not add /api/bms/billing/* compatibility routes for those names; future P&L or billing report work should use a named report-owned API v2 route.
  • Transaction popup billing address lookup uses GET /api/bms/companies/{id}/billing-address; callers must not reintroduce BILLING_ADDRESS_BY_COMPANY_ID.
  • GET /api/bms/reports/trucking-summary and GET /api/bms/reports/trucking-summary/driver-transactions own the trucking summary report reads. POST /api/bms/reports/trucking-summary/refresh-ap-rates recalculates zero T_DELIVERY.AP_TRUCKING_RATE values with no AP transaction for one driver/date range by reusing the trucking-rate lookup rules. This is BMS/report boundary work and no longer uses the shared legacy query contract.
  • BMS rate, scale-ticket, invoice read, transaction action, and trucking summary active callers use API v2 request envelopes, not TYPE/ACTION/USERNAME.

Booking

  • MK booking editor persists PICKUP_TERMINAL_ID, RETURN_TERMINAL_ID, PORT_OF_LOADING, PORT_OF_DISCHARGE, MINIMUM_WEIGHT, and MAXIMUM_WEIGHT on T_BOOKING.
  • GET /api/tms/bookings/search returns bounded suggestions and items for booking autocomplete, including booking number, shipping line, vessel, cutoff/SI cutoff, POL/POD, and 20S/40S/40H/40F/45H quantity columns.
  • GET /api/tms/bookings/drayage-empty-options returns bounded current Export Dashboard export -trucking booking options with booking, quantity, billing company, and terminal ids for Export Dispatch Drayage Queue empty creation. Optional from/to query params apply the same CUTOFF date window used by the dashboard booking list; Export Dispatch passes from=today by default.
  • MK booking create/update requires at least one shipper company in SHIPPER_COMPANY_ID_LIST; the primary SHIPPER_COMPANY_ID mirrors the first selected company.
  • Booking confirmation print reads terminal display names from T_COMPANY and prints POL/POD/weight values from saved booking data with visible - fallbacks for blanks.
  • tms.booking owns MK_BOOKING, MK_BOOKING_*, booking list/detail/options, packing list reads/files/save/email, booking loading options, duplicate reference checks, weight requirement checks, clone booking, empty-plan summary/create/booking inquiry email, and export-container revert routes.
  • API v2 routes:
  • GET /api/tms/bookings
  • POST /api/tms/bookings
  • POST /api/tms/bookings/booking-instruction/ai-extract
  • GET/PATCH /api/tms/bookings/booking-instruction/ai-prompt
  • POST/PATCH /api/tms/bookings/form
  • GET /api/tms/bookings/{id}
  • PATCH /api/tms/bookings/{id}
  • POST /api/tms/bookings/{id}/clone
  • GET /api/tms/bookings/{id}/containers
  • GET /api/tms/bookings/{id}/packing-list
    • Optional query parameter EQUIPMENT_LOAD_ID / equipmentLoadId filters the MK packing list to one container. When omitted for MK, the packing list returns all valid linked booking containers.
  • GET /api/tms/bookings/{id}/packing-list/files
  • POST /api/tms/bookings/{id}/packing-list/email
  • PATCH /api/tms/bookings/{id}/packing-list/equipment-loads
  • PATCH /api/tms/bookings/{id}/packing-list/rates
    • Updates packing-list sales order item RATE. The displayed invoice weight unit is a report rendering choice and is not persisted to T_LOAD_ITEM.UOM; callers that let users edit rates in a temporary display UOM must convert the submitted RATE back to the stored order-item billing UOM basis.
  • GET /api/tms/bookings/options
  • GET /api/tms/bookings/drayage-empty-options
  • GET /api/tms/bookings/loading-options
  • GET /api/tms/bookings/weight-requirement
  • POST /api/tms/bookings/duplicate-reference
  • GET /api/tms/empty-plan/summary
  • POST /api/tms/empty-plan/create
  • POST /api/tms/empty-plan/booking-inquiry-email
  • POST /api/tms/export-containers/revert

Carrier

  • tms.carrier owns carrier master lookup/export routes used by TMS appointment, inbound planning, popup, and table export flows.
  • GET /api/tms/carriers/by-phone looks up carrier master rows by phone number.
  • GET /api/tms/carriers/{id} and POST/PATCH /api/tms/carriers/form own carrier popup detail and form writes.
  • POST /api/tms/carriers/export-master returns the export list and marks exported carrier master rows through the existing helper, so treat it as a write route.

Customer PO bridge

  • POST /api/oms/customer-pos/by-load-numbers is the API v2 bridge for popup load-number to customer-PO lookup.
  • GET /api/oms/customer-pos/{id} and /items are the API v2 bridge for customer-PO popup edit reads.
  • POST /api/oms/customer-pos owns customer-PO popup create with item rows; POST/PATCH /api/oms/customer-pos/form owns scoped form updates/deletes.
  • Ownership remains oms.inbound-order; booking/transload callers must not call shared query.php for GET_CUSTOMER_PO_BY_LOAD_NUMBERS.

Inbound planning

  • Read board: GET /api/tms/transloads/planning-board.
  • Load schedule AI extraction: POST /api/tms/transloads/load-schedule/ai-extract; prompt read/save: GET/PATCH /api/tms/transloads/load-schedule/ai-prompt.
  • Export transload schedule target extraction: POST /api/tms/transloads/export-schedule/ai-extract; the response has no availability or write authority.
  • Check-in: POST /api/tms/transloads/check-in.
  • Preview: POST/PATCH /api/tms/transloads/plan-preview.
  • Apply plan: POST/PATCH /api/tms/transloads/plan-apply.
  • Confirm: POST/PATCH /api/tms/transloads/assign-submit.
  • Planner form/event writes: POST/PATCH /api/tms/loading-planner/form, PATCH /api/tms/loading-planner/{id}, and PATCH /api/tms/transload-planner/events/{id}.
  • Runtime: /api/tms/transloads/dispatch, /cancel, /no-show, /confirm-plan, /plan, /mobile-check-in, /dock-in, /stage, /start-*, /complete-*, and /undo.
  • Loader work queue: GET/POST /api/tms/transloads/work-assignments; Loader-only GET /work-assignments/current conceals scheduled-assignment detail until POST /work-assignments/{id}/start; POST /complete, /advance-clock-out, and /clock-out retain the existing T_LOADING_PLANNER and staff-time event authority.

Email reschedule

  • Read decision: GET /api/tms/transload-emails/reschedule-decision.
  • Write apply: POST /api/tms/transload-emails/reschedule-apply.
  • Allowed source statuses: SCHEDULED, PRE_PLANNED.
  • Possible decisions: EXACT_AVAILABLE, NEXT_AVAILABLE, MANUAL_CONTACT, NO_CHANGE.
  • Menu management uses API v2 routes under /api/platform/menu/*.
  • Menu reads: GET /api/platform/menu/tree, GET /api/platform/menu/roles, GET /api/platform/module-pages.
  • Menu writes: POST /api/platform/menu/items, PATCH/DELETE /api/platform/menu/items/{id}, PATCH /api/platform/menu/tree, and PATCH /api/platform/menu/roles/{id}/default-menu.
  • Menu writes clear KJ_MENU_CACHE after a successful commit.

AI extraction

  • Legacy compatibility endpoint: app/controllers/chatgpt.php.
  • Active browser AI callers use KJ.api.ai() and KJ.api.aiPrompt() through public/assets/vendor/kj/kj.js; direct seal-photo OCR callers require the CONTAINER_SEAL_PHOTO owner route. Unsupported browser task or prompt names fail visibly and do not call chatgpt.php.
  • Owner API v2 routes are active for all browser extraction tasks: IMPORT_DELIVERY_ORDER, LOAD_SCHEDULE, BOOKING_INSTRUCTION, DOORTRADE_PACKING_LIST, EXPORT_BROKERAGE_DOCUMENT, INGATE_TICKET, and CONTAINER_SEAL_PHOTO.
  • chatgpt.php remains a compatibility surface for direct legacy/external AI callers and tenant prompt actions not yet exposed through owner prompt routes. Delete it only after the controller-specific source inventory and runtime tests confirm active browser/API flows use owner API v2 routes; direct external legacy usage would receive 404 by design after deletion.
  • APPOINTMENT_ACTION_PLAN is registered in AiTaskRegistry, but transload appointment email intake calls TransloadAppointmentActionAgent directly rather than routing through chatgpt.php.
  • GET_PROMPT and SAVE_PROMPT remain supported controller actions for direct legacy/external prompt editing where no owner prompt route exists; browser prompt editing must use owner GET/PATCH prompt endpoints.
  • Preferred request: multipart form-data with a file.
  • JSON request remains supported for existing callers.
  • Env vars: GEMINI_KEY, fallback GEMINI_API_KEY.
  • Prompt overrides live in media/ai-prompts/<TASK_NAME>.txt.

Naming Rules

  • Keep uppercase TYPE and ACTION values.
  • New direct endpoints should keep existing envelopes until all callers are migrated.
  • Do not introduce compatibility dispatchers or pass-through wrapper functions.
  • Add a module guard mapping when adding a new endpoint or TYPE.
  • Do not use legacy naming rules as precedent for API v2 routes; API v2 routes must use explicit domain/module paths.

Migration Completion Criteria

Local TMS Assignment Work candidate

  • API v2 routes under /api/tms/transloads/assignment-work provide manager batch create/list/edit/cancel/Telegram retry and Loader concealed-current/start/complete behavior.
  • Started Clock Out adds Loader-only cleaning-complete, final-break, and clock-out actions; general /api/tms/staff-time/event Clock Out rejects while that workflow is Started.
  • final-break is available only for a Started Clock Out assignment with finalBreakRequired=true; the server enforces cleaning completion, one linked BREAK_IN, and a full 10 server minutes before workflow Clock Out creates the linked BREAK_OUT.
  • Responses retain standard v2 envelopes. Local assignment commit is independent from Telegram or QuickBooks provider outcome; linked Final Break BREAK_OUT suppresses only the regular-work QuickBooks resume path.

  • Frontend caller uses the module direct endpoint.

  • Shared controller route remains removed; do not restore it for compatibility.
  • Module gate behavior matches shared endpoint behavior.
  • Error envelope is visible to the user.
  • Tests or smoke checks cover both enabled and disabled module paths.
  • For API v2 cutover, all active TYPE/ACTION callers must be migrated, deleted, or explicitly deferred in .agents/rules/API_V2_CONTRACT_INVENTORY.md.

---

Source: .agents/rules/API_CONTRACTS.md — ctc repository · This page is an English translation of the source document. Identifiers, file paths and code symbols are unchanged.