283 lines
15 KiB
YAML
283 lines
15 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Carrier Schedule Query API
|
|
version: 1.0.0
|
|
description: Phase-4 carrier tracking from domestic outbound confirmation through actual empty return.
|
|
paths:
|
|
/api/platform/v1/carrier-schedule-queries/carriers:
|
|
get:
|
|
summary: List official carrier sources and compliant access modes
|
|
x-permission: platform:firstleg:port-schedule-query:list
|
|
responses:
|
|
'200': {description: 'MATSON, SML, ZIM, COSCO, EMC and HEDE descriptors'}
|
|
/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'}}}
|
|
/api/platform/v1/carrier-schedule-queries:
|
|
get:
|
|
summary: Query carrier schedule tasks
|
|
x-permission: platform:firstleg:port-schedule-query:list
|
|
parameters:
|
|
- {name: customerId, in: query, schema: {type: integer, format: int64, minimum: 1}}
|
|
- {name: oceanBookingId, in: query, schema: {type: integer, format: int64, minimum: 1}}
|
|
- {name: containerBookingId, in: query, schema: {type: integer, format: int64, minimum: 1}}
|
|
- {name: carrierCode, in: query, schema: {$ref: '#/components/schemas/TaskCarrierCode'}}
|
|
- {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:
|
|
'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}
|
|
post:
|
|
summary: Create a query task linked to a real booking and allocated container
|
|
x-permission: platform:firstleg:port-schedule-query:create
|
|
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:
|
|
summary: Get task, standardized node facts and immutable business history
|
|
x-permission: platform:firstleg:port-schedule-query:list
|
|
responses:
|
|
'200':
|
|
description: Stable task detail
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}
|
|
/api/platform/v1/carrier-schedule-queries/{queryId}/history:
|
|
parameters: [{$ref: '#/components/parameters/QueryId'}]
|
|
get:
|
|
summary: Get immutable task history
|
|
x-permission: platform:firstleg:port-schedule-query:list
|
|
responses:
|
|
'200': {description: Immutable business history; scheduler attempts are not exposed}
|
|
/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
|
|
description: This legacy port-schedule association does not append a standard node and never stops tracking; phase-4 manual facts use /manual-nodes.
|
|
x-permission: platform:firstleg:port-schedule-query:manual
|
|
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: {$ref: '#/components/schemas/ManualResultRequest'}
|
|
responses:
|
|
'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':
|
|
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
|
|
'409':
|
|
description: A0409 when expectedVersion is stale or a manual actual node is equal to or earlier than latestActualNode
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}
|
|
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]}
|
|
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.
|
|
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."}
|
|
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]}
|
|
CreateRequest:
|
|
type: object
|
|
required: [expectedVersion, customerId, oceanBookingId, containerBookingId, carrierCode, trackingType, trackingNo]
|
|
properties:
|
|
expectedVersion: {type: integer, minimum: 0, maximum: 0}
|
|
customerId: {type: integer, format: int64, minimum: 1}
|
|
oceanBookingId: {type: integer, format: int64, minimum: 1}
|
|
containerBookingId: {type: integer, format: int64, minimum: 1}
|
|
carrierCode: {$ref: '#/components/schemas/CarrierCode'}
|
|
trackingType: {$ref: '#/components/schemas/TrackingType'}
|
|
trackingNo: {type: string, minLength: 1, maxLength: 128, description: Normalized physical containerNo; must equal the linked Firstleg container snapshot.}
|
|
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}
|
|
ManualNodeRequest:
|
|
type: object
|
|
required: [expectedVersion, standardNode, observedAt, reason]
|
|
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.
|
|
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
|
|
required: [id, queryNo, customerId, oceanBookingId, containerBookingId, carrierCode, trackingType, trackingNo, status, triggerSource, version]
|
|
properties:
|
|
id: {type: integer, format: int64}
|
|
queryNo: {type: string}
|
|
customerId: {type: integer, format: int64}
|
|
oceanBookingId: {type: integer, format: int64}
|
|
containerBookingId: {type: integer, format: int64}
|
|
carrierCode: {$ref: '#/components/schemas/TaskCarrierCode'}
|
|
trackingType: {$ref: '#/components/schemas/TrackingType'}
|
|
trackingNo: {type: string}
|
|
officialUrl: {type: string, nullable: true}
|
|
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.
|
|
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}
|
|
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.
|
|
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}
|
|
QueryView:
|
|
type: object
|
|
required: [query, expired]
|
|
properties:
|
|
query: {$ref: '#/components/schemas/Task'}
|
|
expired: {type: boolean}
|
|
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'}}
|
|
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'}
|