Migrate from project44
A client built for project44's v4 ocean tracking API can talk to Dockflow by changing two values. The paths, the request bodies and the response shapes stay as they are.
The responses and error bodies on this surface are checked by contract tests against project44's public API document.
What to change
| Setting | Before | After |
|---|---|---|
| Base URL | Your project44 base URL | https://api.dockflow.com/compat/project44 |
| Client secret | Your project44 client secret | A Dockflow API key |
The client_id can stay. Dockflow only needs it to be present.
Get a token
- Sign in to the API Console and open API Keys.
- Click Create Key, choose the Production environment and the Downstream scope.
- Copy the key. It is shown only once.
POST /api/v4/oauth2/token accepts a form body, a JSON body or Basic authentication, with grant_type=client_credentials. It returns your secret as access_token, with token_type: BEARER and expires_in: 3600. The exchange does not check the secret. The first real call does, and the key itself does not expire on that clock.
See Authorization for the key format and for revoking a key.
What is covered
All paths are under /api/v4.
| Route | What it does |
|---|---|
POST /oauth2/token | The token exchange above. |
POST /shipments/tracking | Starts tracking. With id in the body it adds identifiers to that shipment. Answers 200 with the described shipment. |
PUT /shipments/tracking | As POST, and the list you send replaces the stored list for each identifier class you name: bills of lading, bookings or containers. |
GET /shipments/{id}/tracking | The shipment. |
GET /shipments/{id}/tracking/history | Shipment, events, states, positions. This is also the webhook body. |
GET /shipments/{id}/eventhistory | Events and states. Add includeRouteInfo=true for the route. |
GET /shipments/{id}/positionhistory | Positions, which are empty. Add includeRouteInfo=true for the route. |
GET /shipments/{id}/attributes | An empty list. |
DELETE /shipments/{id}/tracking | 202. A parent id stops tracking and removes the shipment. A container id removes that container. |
Any /webhooks path | 403. Destinations are set outside this API, see Webhooks. |
Not part of this surface: /api/v4/ocean/shipments, /services/unified-search, loads and carrier push paths. There is no read by identifier: read by id.
Identity
One tracked object is one Dockflow tradeflow. The same set of tracking identifiers reaches the same shipment. The shipment id is a UUID derived from the tradeflow, and each container has its own child id that works on the read and delete routes.
Errors
Errors use the ApiError shape: httpStatusCode, httpMessage, errorMessage and errors. A token refusal has the same shape, with 401 or 403. The token route uses error and error_description.
Field mapping
Identifiers you send
| Type | Dockflow |
|---|---|
BILL_OF_LADING | Bill of lading reference. |
BOOKING_NUMBER | Booking reference. |
CONTAINER_ID, relatedShipments | Container reference. CONTAINER_NUMBER is accepted as an alias. |
CARRIER_SCAC | Carrier SCAC. No SCAC is required next to a bill or booking. |
FFW_SCAC | Accepted. It is only repeated in the answer to the same call. |
HOUSE_BILL_OF_LADING, TRACKING_NUMBER | Stored and repeated in the answer to the create. Later reads do not show them, and they do not start tracking by themselves. |
Other types answer 400.
Stops
| Dockflow role | type |
|---|---|
| Port of loading | PORT_OF_LOADING |
| Port of discharge | PORT_OF_DISCHARGE |
| Transshipment | TRANSSHIPMENT_PORT |
| Before the port of loading | ORIGIN |
| After the port of discharge | DESTINATION |
| Anything else | OTHER |
Events
| Dockflow code | type |
|---|---|
GE | GATE_OUT_EMPTY |
GN | GATE_IN_FULL |
AE | LOAD |
VD | DEPARTURE_FROM_STOP |
VA | ARRIVAL_AT_STOP |
UV | DISCHARGE |
GT, GD | GATE_OUT_FULL |
GR | GATE_IN_EMPTY |
details.ocean.code is filled when the event names its port role, for example VESSEL_DEPARTURE_FROM_TRANSSHIPMENT_PORT. When Dockflow does not know the role, the field is absent.
dateTimes holds PLANNED, ESTIMATE and ACTUAL entries, with selected on the one the event shows. A container on an event sits in dateTimes[].sourceIdentifiers as CONTAINER_ID. Identical events of several containers are one parent event that names them all, and a child read shows only its own. source is CARRIER or UNKNOWN.
Other Dockflow events, such as customs events, are left out.
Segments and states
A route segment is OCEAN unless the departure or arrival event states BARGE, RAIL or TRUCKLOAD. A state is in transit between an actual departure and arrival, and AT_STOP between an actual arrival and the next departure.
What stays empty
| Field | Value |
|---|---|
positions, costs, contents, exceptions | []. |
attributes, accessGroups, plan, shipmentShareLink, sharingContext, sensorContext, emissions | Absent. |
receivedDateTime, estimateLastCalculatedDateTime, plannedEndDateTime | Absent. |
location.masterLocationId, street address | Absent. |
| Terminal name | No field carries it. |
routeInfo, events, plan, attributes, accessGroups, costs, contents and shipmentShareLink in a request are not applied. The answer says so in problems.
A tradeflow with several shipments answers reads from its first shipment. Webhooks deliver one body per shipment, under the same id.
Webhooks
The /webhooks routes answer 403. Contact [email protected] to add a destination in the project44-v4 format. You receive its signing secret once. It cannot be read again.
Each delivery is one POST whose body has the shape of GET /shipments/{id}/tracking/history: shipment, events, positions, states, costs, contents and exceptions. Retries, timeouts and ordering follow Webhook delivery.
| Header | Value |
|---|---|
X-Dockflow-Signature | sha256=<hex>: HMAC-SHA256 of the exact body with your signing secret. |
X-Dockflow-Timestamp | Unix seconds. It is not signed, so it gives no replay protection. |
Verify the signature
Compute the HMAC over the exact bytes you received, before any JSON parsing:
import hashlib, hmac
def is_authentic(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)
How to test
- Create a key with the UAT environment and the Downstream scope. It works on
https://uat.dockflow.com/compat/project44and writes to your UAT organisation. A real number is really tracked there. - Exchange and create:
curl -X POST https://uat.dockflow.com/compat/project44/api/v4/oauth2/token \
-d grant_type=client_credentials -d client_id=acme \
-d client_secret=uat_acme-logistics_Zk3n1c0Q2m9...
curl -X POST https://uat.dockflow.com/compat/project44/api/v4/shipments/tracking \
-H "Authorization: Bearer uat_acme-logistics_Zk3n1c0Q2m9..." \
-H "Content-Type: application/json" \
-d '{"identifiers":[{"type":"BILL_OF_LADING","value":"ANR00001010"},{"type":"CARRIER_SCAC","value":"MAEU"}]}'
- Read
GET /api/v4/shipments/{id}/tracking/historywith theidfrom the answer. The import runs after the answer, so the first read can be short. - To test a webhook, open the destination in the console and click Send test with the Live tradeflow scenario, naming the stored reference of a tradeflow that has data. The Ocean basic scenario is refused for this format.
Polling is also possible: GET /v3/upstream?format=project44-v4 with an Upstream key returns these same history objects. See Upstream filters.