2026-08-10 12:20:05 +08:00
openapi : 3.0.3
info :
title : Carrier Schedule Query API
version : 1.0.0
2026-08-21 16:04:16 +08:00
description : Phase-4 carrier tracking from domestic outbound confirmation through actual empty return.
2026-08-10 12:20:05 +08:00
paths :
/api/platform/v1/carrier-schedule-queries/carriers :
get :
summary : List official carrier sources and compliant access modes
2026-08-20 09:10:30 +08:00
x-permission : platform:firstleg:port-schedule-query:list
2026-08-10 12:20:05 +08:00
responses :
2026-08-25 12:27:47 +08:00
'200' : {description : 'MATSON, SML, ZIM, COSCO, EMC and HEDE descriptors' }
2026-09-08 19:09:08 +08:00
/api/platform/v1/carrier-schedule-queries/container-actual-facts :
get :
summary : Batch query actual carrier-node facts for container workbenches
description : Returns at most one row for each container that has a carrier-schedule query. Missing rows mean that no query exists. Estimated facts are never returned or inferred.
x-permission : platform:firstleg:port-schedule-query:list
parameters :
- name : containerBookingIds
in : query
required : true
schema :
type : array
minItems : 1
maxItems : 200
uniqueItems : true
items : {type : integer, format : int64, minimum : 1 }
responses :
'200' :
description : Actual-only summaries ordered by containerBookingId
content : {application/json : {schema : {$ref : '#/components/schemas/ContainerActualFactsListEnvelope' }}}
2026-08-10 12:20:05 +08:00
/api/platform/v1/carrier-schedule-queries :
get :
summary : Query carrier schedule tasks
2026-08-20 09:10:30 +08:00
x-permission : platform:firstleg:port-schedule-query:list
2026-08-10 12:20:05 +08:00
parameters :
2026-09-10 11:55:09 +08:00
- {name : customerId, in : query, schema : {type : integer, format : int64, minimum : 0 }}
2026-08-10 12:20:05 +08:00
- {name : oceanBookingId, in : query, schema : {type : integer, format : int64, minimum : 1 }}
- {name : containerBookingId, in : query, schema : {type : integer, format : int64, minimum : 1 }}
2026-08-26 14:49:11 +08:00
- {name : carrierCode, in : query, schema : {$ref : '#/components/schemas/TaskCarrierCode' }}
2026-08-10 12:20:05 +08:00
- {name : status, in : query, schema : {$ref : '#/components/schemas/QueryStatus' }}
- {name : expired, in : query, schema : {type : boolean}}
- {name : keyword, in : query, schema : {type : string, maxLength : 128 }}
- {name : page, in : query, schema : {type : integer, minimum : 1, default : 1 }}
- {name : size, in : query, schema : {type : integer, minimum : 1, maximum : 200, default : 20 }}
responses :
2026-08-21 17:05:52 +08:00
'200' : {description : Query tasks with source and freshness fields; normal execution uses fixed Shanghai slots while retryable technical failures may retry within the current slot}
2026-08-10 12:20:05 +08:00
post :
summary : Create a query task linked to a real booking and allocated container
2026-08-20 09:10:30 +08:00
x-permission : platform:firstleg:port-schedule-query:create
2026-08-10 12:20:05 +08:00
parameters : [ {$ref : '#/components/parameters/IdempotencyKey' }]
requestBody :
required : true
content :
application/json :
schema : {$ref : '#/components/schemas/CreateRequest' }
responses :
'200' : {description : Created task or idempotent replay}
/api/platform/v1/carrier-schedule-queries/{queryId}:
parameters : [ {$ref : '#/components/parameters/QueryId' }]
get :
2026-08-20 18:02:38 +08:00
summary : Get task, standardized node facts and immutable business history
2026-08-20 09:10:30 +08:00
x-permission : platform:firstleg:port-schedule-query:list
2026-08-10 12:20:05 +08:00
responses :
2026-08-21 16:04:16 +08:00
'200' :
description : Stable task detail
content : {application/json : {schema : {$ref : '#/components/schemas/DetailEnvelope' }}}
2026-08-10 12:20:05 +08:00
/api/platform/v1/carrier-schedule-queries/{queryId}/history:
parameters : [ {$ref : '#/components/parameters/QueryId' }]
get :
summary : Get immutable task history
2026-08-20 09:10:30 +08:00
x-permission : platform:firstleg:port-schedule-query:list
2026-08-10 12:20:05 +08:00
responses :
2026-08-21 16:04:16 +08:00
'200' : {description : Immutable business history; scheduler attempts are not exposed}
2026-08-10 12:20:05 +08:00
/api/platform/v1/carrier-schedule-queries/{queryId}/manual-results:
parameters : [ {$ref : '#/components/parameters/QueryId' }]
post :
summary : Link a validated manual port-schedule observation without pretending automated success
2026-08-21 16:04:16 +08:00
description : This legacy port-schedule association does not append a standard node and never stops tracking; phase-4 manual facts use /manual-nodes.
2026-08-20 09:10:30 +08:00
x-permission : platform:firstleg:port-schedule-query:manual
2026-08-10 12:20:05 +08:00
parameters : [ {$ref : '#/components/parameters/IdempotencyKey' }]
requestBody :
required : true
content :
application/json :
schema : {$ref : '#/components/schemas/ManualResultRequest' }
responses :
2026-08-21 16:04:16 +08:00
'200' :
description : Successful manual fallback and next query time
content : {application/json : {schema : {$ref : '#/components/schemas/DetailEnvelope' }}}
/api/platform/v1/carrier-schedule-queries/{queryId}/manual-nodes:
parameters : [ {$ref : '#/components/parameters/QueryId' }]
post :
summary : Append one immutable manual estimated or actual standard-node fact
x-permission : platform:firstleg:port-schedule-query:manual
parameters : [ {$ref : '#/components/parameters/IdempotencyKey' }]
requestBody :
required : true
content :
application/json :
schema : {$ref : '#/components/schemas/ManualNodeRequest' }
responses :
'200' :
2026-08-26 15:44:05 +08:00
description : Idempotent fact append; estimated facts never advance actual progress, while a manual actual fact must have a strictly greater standard-node sequence than latestActualNode and actual EMPTY_RETURNED stops
2026-08-26 15:41:03 +08:00
'409' :
2026-08-26 15:44:05 +08:00
description : A0409 when expectedVersion is stale or a manual actual node is equal to or earlier than latestActualNode
2026-08-21 16:04:16 +08:00
content : {application/json : {schema : {$ref : '#/components/schemas/DetailEnvelope' }}}
2026-08-10 12:20:05 +08:00
components :
parameters :
QueryId : {name : queryId, in : path, required : true, schema : {type : integer, format : int64, minimum : 1 }}
IdempotencyKey : {name : Idempotency-Key, in : header, required : true, schema : {type : string, minLength : 1, maxLength : 128 }}
schemas :
CarrierCode : {type : string, enum : [ MATSON, SML, ZIM, COSCO, EMC, HEDE]}
2026-08-26 14:48:43 +08:00
TaskCarrierCode :
type : string
enum : [ MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER]
description : OTHER is frozen on the ocean booking and creates a manual-only task after outbound confirmation.
2026-09-09 12:54:14 +08:00
TrackingType : {type : string, enum : [CONTAINER_NO], description : "Only the normalized physical containerNo frozen by Firstleg is accepted; cabinetNo, orderNo, bookingNo and bill of lading are never fallbacks." }
2026-08-21 16:04:16 +08:00
QueryStatus : {type : string, enum : [ READY, MANUAL_REQUIRED, STOPPED]}
StandardNode :
type : string
enum : [ DEPARTED_WAREHOUSE, AT_ORIGIN_PORT, LOADED_ON_VESSEL, DEPARTED_ORIGIN_PORT, IN_TRANSIT, ARRIVED_DESTINATION_PORT, DISCHARGED_FROM_VESSEL, PICKUP_READY, PICKED_UP, EMPTY_RETURNED]
ExceptionCode : {type : string, enum : [ CUSTOMS_HOLD, TERMINAL_HOLD, DOCUMENT_HOLD, UNKNOWN_HOLD]}
2026-08-10 12:20:05 +08:00
CreateRequest :
type : object
2026-08-31 12:13:20 +08:00
required : [ expectedVersion, customerId, oceanBookingId, containerBookingId, carrierCode, trackingType, trackingNo]
2026-08-10 12:20:05 +08:00
properties :
expectedVersion : {type : integer, minimum : 0, maximum : 0 }
2026-09-10 11:55:09 +08:00
customerId : {type : integer, format : int64, minimum : 0 }
2026-08-10 12:20:05 +08:00
oceanBookingId : {type : integer, format : int64, minimum : 1 }
2026-08-31 12:13:20 +08:00
containerBookingId : {type : integer, format : int64, minimum : 1 }
2026-08-10 12:20:05 +08:00
carrierCode : {$ref : '#/components/schemas/CarrierCode' }
trackingType : {$ref : '#/components/schemas/TrackingType' }
2026-08-31 12:13:20 +08:00
trackingNo : {type : string, minLength : 1, maxLength : 128, description : Normalized physical containerNo; must equal the linked Firstleg container snapshot.}
2026-08-10 12:20:05 +08:00
remark : {type : string, nullable : true, maxLength : 1000 }
ManualResultRequest :
type : object
required : [ expectedVersion, portScheduleId, reason]
properties :
expectedVersion : {type : integer, minimum : 0 }
portScheduleId : {type : integer, format : int64, minimum : 1 }
reason : {type : string, minLength : 1, maxLength : 1000 }
2026-08-21 16:04:16 +08:00
ManualNodeRequest :
type : object
required : [ expectedVersion, standardNode, observedAt, reason]
2026-08-26 15:44:05 +08:00
description : estimatedAt and actualAt cannot both be null; estimatedAt never advances the latest actual node; when actualAt is present, standardNode must have a strictly greater sequence than latestActualNode.
2026-08-21 16:04:16 +08:00
properties :
expectedVersion : {type : integer, minimum : 0 }
standardNode : {$ref : '#/components/schemas/StandardNode' }
observedAt : {type : string, format : date-time}
estimatedAt : {type : string, format : date-time, nullable : true }
actualAt : {type : string, format : date-time, nullable : true }
locationCode : {type : string, maxLength : 32, nullable : true }
locationName : {type : string, maxLength : 128, nullable : true }
reason : {type : string, minLength : 1, maxLength : 1000 }
Task :
type : object
2026-08-31 12:13:20 +08:00
required : [ id, queryNo, customerId, oceanBookingId, containerBookingId, carrierCode, trackingType, trackingNo, status, triggerSource, version]
2026-08-21 16:04:16 +08:00
properties :
id : {type : integer, format : int64}
queryNo : {type : string}
customerId : {type : integer, format : int64}
oceanBookingId : {type : integer, format : int64}
2026-08-31 12:13:20 +08:00
containerBookingId : {type : integer, format : int64}
2026-08-26 14:48:43 +08:00
carrierCode : {$ref : '#/components/schemas/TaskCarrierCode' }
2026-08-21 16:04:16 +08:00
trackingType : {$ref : '#/components/schemas/TrackingType' }
trackingNo : {type : string}
officialUrl : {type : string, nullable : true }
2026-08-26 14:48:43 +08:00
accessMode :
type : string
enum : [ PUBLIC_FORM, PUBLIC_WEB_PROXY, PUBLIC_HTML, HCAPTCHA, OFFICIAL_API, MANUAL]
nullable : true
description : MANUAL tasks preserve physical outbound confirmation and accept platform manual node facts without scheduler/provider calls.
2026-08-21 16:04:16 +08:00
status : {$ref : '#/components/schemas/QueryStatus' }
triggerSource : {type : string}
latestActualNode : {allOf : [ {$ref : '#/components/schemas/StandardNode' }], nullable : true }
latestActualAt : {type : string, format : date-time, nullable : true }
lastSuccessAt : {type : string, format : date-time, nullable : true }
sourceUpdatedAt : {type : string, format : date-time, nullable : true }
expiresAt : {type : string, format : date-time, nullable : true }
resultPortScheduleId : {type : integer, format : int64, nullable : true }
2026-08-26 14:48:43 +08:00
manualReason :
type : string
nullable : true
description : Explicit provider-disabled or unsupported-carrier reason; unsupported outbound carriers retain the booking shippingCompanyId/name here and through oceanBookingId.
2026-08-21 16:04:16 +08:00
lastErrorCode : {type : string, nullable : true }
lastErrorMessage : {type : string, nullable : true }
stoppedAt : {type : string, format : date-time, nullable : true }
stopReason : {type : string, nullable : true }
version : {type : integer, minimum : 0 }
2026-08-10 12:20:05 +08:00
QueryView :
type : object
2026-08-20 18:02:38 +08:00
required : [ query, expired]
2026-08-10 12:20:05 +08:00
properties :
2026-08-21 16:04:16 +08:00
query : {$ref : '#/components/schemas/Task' }
2026-08-10 12:20:05 +08:00
expired : {type : boolean}
2026-09-08 19:09:08 +08:00
ContainerActualFacts :
type : object
required : [ containerBookingId, queryId, queryVersion, queryStatus]
description : Actual facts only. All node times are null until an immutable fact with actualAt exists; estimatedAt is intentionally absent.
properties :
containerBookingId : {type : integer, format : int64}
queryId : {type : integer, format : int64}
queryVersion : {type : integer, minimum : 0 }
queryStatus : {$ref : '#/components/schemas/QueryStatus' }
latestActualNode : {allOf : [ {$ref : '#/components/schemas/StandardNode' }], nullable : true }
latestActualAt : {type : string, format : date-time, nullable : true }
departedWarehouseAt : {type : string, format : date-time, nullable : true }
departedOriginPortAt : {type : string, format : date-time, nullable : true }
arrivedDestinationPortAt : {type : string, format : date-time, nullable : true }
pickupReadyAt : {type : string, format : date-time, nullable : true }
pickedUpAt : {type : string, format : date-time, nullable : true }
emptyReturnedAt : {type : string, format : date-time, nullable : true }
ContainerActualFactsListEnvelope :
type : object
required : [ code, data]
properties :
code : {type : string, example : '200' }
message : {type : string, nullable : true }
data : {type : array, items : {$ref : '#/components/schemas/ContainerActualFacts' }}
2026-08-21 16:04:16 +08:00
NodeFact :
type : object
required : [ id, providerCode, providerEventId, providerNodeCode, standardNode, nodeSequence, observedAt, rawPayloadHash, createdAt]
properties :
id : {type : integer, format : int64}
providerCode : {type : string, maxLength : 32 }
providerEventId : {type : string, maxLength : 128 }
providerNodeCode : {type : string, maxLength : 64 }
standardNode : {$ref : '#/components/schemas/StandardNode' }
nodeSequence : {type : integer, enum : [ 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 , 100 ] }
observedAt : {type : string, format : date-time}
estimatedAt : {type : string, format : date-time, nullable : true }
actualAt : {type : string, format : date-time, nullable : true }
locationCode : {type : string, maxLength : 32, nullable : true }
locationName : {type : string, maxLength : 128, nullable : true }
sourceReference : {type : string, maxLength : 500, nullable : true }
rawPayloadHash : {type : string, pattern : '^[0-9a-f]{64}$' }
createdAt : {type : string, format : date-time}
ExceptionFact :
type : object
required : [ id, providerCode, providerEventId, providerStatus, exceptionCode, active, observedAt, rawPayloadHash, createdAt]
properties :
id : {type : integer, format : int64}
providerCode : {type : string, maxLength : 32 }
providerEventId : {type : string, maxLength : 128 }
providerStatus : {type : string, maxLength : 128 }
exceptionCode : {$ref : '#/components/schemas/ExceptionCode' }
active : {type : boolean, description : true means hold occurred; false means released}
observedAt : {type : string, format : date-time}
locationCode : {type : string, maxLength : 32, nullable : true }
locationName : {type : string, maxLength : 128, nullable : true }
sourceReference : {type : string, maxLength : 500, nullable : true }
rawPayloadHash : {type : string, pattern : '^[0-9a-f]{64}$' }
createdAt : {type : string, format : date-time}
History :
type : object
required : [ id, action, createdAt]
properties :
id : {type : integer, format : int64}
action : {type : string, enum : [ CREATE, AUTO_CREATE, MANUAL_RESULT, MANUAL_NODE, NODE_OBSERVED, EXCEPTION_OBSERVED, PROVIDER_QUERY]}
fromVersion : {type : integer, nullable : true }
toVersion : {type : integer, nullable : true }
reason : {type : string, nullable : true }
operatorId : {type : integer, format : int64, nullable : true }
operatorName : {type : string, nullable : true }
createdAt : {type : string, format : date-time}
Detail :
type : object
required : [ query, nodes, exceptions, histories]
description : Scheduler lease, attempt and internal execution fields are intentionally absent.
properties :
query : {$ref : '#/components/schemas/QueryView' }
nodes : {type : array, items : {$ref : '#/components/schemas/NodeFact' }}
exceptions : {type : array, items : {$ref : '#/components/schemas/ExceptionFact' }}
histories : {type : array, items : {$ref : '#/components/schemas/History' }}
DetailEnvelope :
type : object
required : [ code, data]
properties :
code : {type : string, example : '200' }
message : {type : string, nullable : true }
data : {$ref : '#/components/schemas/Detail' }