Migrate from ShipsGo
A client built for the ShipsGo v2 ocean 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 ShipsGo's public OpenAPI document.
What to change
| Setting | Before | After |
|---|---|---|
| Base URL | Your ShipsGo v2 base URL | https://api.dockflow.com/compat/shipsgo/v2 |
X-Shipsgo-User-Token | Your ShipsGo token | A Dockflow API key |
The header name does not change.
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.
See Authorization for the key format and for revoking a key.
What is covered
| Route | What it does |
|---|---|
POST /ocean/shipments | Starts tracking. Answers 200 with message: SUCCESS and the short shipment. A shipment that exists already answers 409 with message: ALREADY_EXISTS and that shipment. |
GET /ocean/shipments | The list, with skip and take (default 25, at most 100). meta.more says if another page follows, and meta.total is null while it does. |
GET /ocean/shipments/{id} | One shipment, with its containers and movements. |
PATCH /ocean/shipments/{id} | Changes reference. |
DELETE /ocean/shipments/{id} | Stops tracking and removes the shipment. |
GET /ocean/carriers | The carriers Dockflow knows, with skip and take. Filters: filters[status]=eq: and filters[name]=contains:. Every carrier reports status: ACTIVE. |
POST and DELETE on followers and tags answer 403 with message: FORBIDDEN. GET /ocean/shipments/{id}/geojson answers 404. Air routes are not part of this surface.
Identity
The duplicate rule is the client reference plus the booking number, or plus the container number when there is no booking. Dockflow stores this pair as the tradeflow reference: <reference>~<NUMBER>, or SHIPSGO-<NUMBER> without a client reference. This stored form is what your colleagues see on the tradeflow in Dockflow.
A duplicate POST still sends its identifiers to the import, so it can add a container to the existing shipment.
Errors
| Status | message | When |
|---|---|---|
401 | UNAUTHORIZED | The token is missing, malformed or unknown. |
403 | FORBIDDEN | The token is revoked or lacks the scope, or a PATCH or DELETE touches a shipment another organisation created. |
404 | NOT_FOUND | No shipment with this id belongs to your organisation. |
409 | ALREADY_EXISTS | See above. |
422 | VALIDATION_FAILED | The body holds errors, a map of field to problem. |
Field mapping
Create
| ShipsGo field | Dockflow |
|---|---|
reference | The first part of the tradeflow reference. 5 to 128 characters. |
booking_number | Booking reference. A master bill of lading sent in this field is also stored as a booking reference. |
container_number | Container reference. |
carrier | Passed on as the carrier name of that SCAC. An unknown SCAC or an SG_ code is accepted and not passed on. |
followers, tags | Validated and dropped. No follower mail is sent. |
Events
| Dockflow code | event |
|---|---|
GE | EMSH |
GN | GTIN |
AE | LOAD |
VD | DEPA |
VA | ARRV |
UV | DISC |
GT, GD | GTOT |
GR | EMRT |
A movement has status: ACT when it happened and status: EST when it is expected. Dockflow events with other codes are left out.
Shipment status
status | When |
|---|---|
UNTRACKED | Inactive and no events. |
NEW | Active, with no events, no plan, no containers and no update yet. |
INPROGRESS | Active with no events, once a plan, a container or an update exists. |
DISCHARGED | An actual empty return, or an actual discharge, gate out or delivery at the port of discharge. |
ARRIVED | An actual vessel arrival at the port of discharge. |
SAILING | An actual departure, or an actual arrival or discharge at another port. |
LOADED | An actual load. |
BOOKED | Events exist, none of the above. |
Container status
A container takes the status of its latest actual movement: EMPTY_SHIPPER, GATE_IN, LOADED, SAILING, ARRIVED, DISCHARGED, GATE_OUT or EMPTY_RETURN. An arrival or discharge away from the port of discharge reads SAILING. Without an actual movement the status is UNKNOWN.
type is HC for an ISO high cube general purpose code, and the ISO code otherwise.
What stays empty
| Field | Value |
|---|---|
tokens | {}. Dockflow has no map link. |
creator | {}. |
followers, tags | Always []. |
checked_at, route.co2_emission | null. |
route | null until the plan knows a port of loading and a port of discharge. |
route.port_of_loading.date_of_loading_initial, route.port_of_discharge.date_of_discharge_initial | Absent when no event carries them. |
route.port_of_discharge.date_of_discharge_predicted | null when no event carries it. |
event.triggered_by.email | Absent. |
X-Shipsgo-Credits-*, X-RateLimit-* response headers | Absent. |
booking_number and container_number show only the identifying number until the import has run. discarded_at holds the last change of an inactive shipment, because Dockflow stores no separate deactivation time.
What behaves differently
- List filters other than
filters[id]=eq:andfilters[container_number]=eq:answer422. Everyorder_byanswers422. - The list is ordered by last change, oldest first.
- Only the
OCEAN.SHIPMENTS.SHIPMENT_UPDATEDwebhook exists.
Webhooks
Webhook destinations are set up on the Dockflow side. Contact [email protected] to add a destination in the shipsgo-v2 format. You receive its signing secret once. It cannot be read again.
Each delivery is one POST with this body:
{
"event": {
"id": "6f1c2b7a-9d3e-4c10-8a55-1e2f3a4b5c6d",
"name": "OCEAN.SHIPMENTS.SHIPMENT_UPDATED",
"triggered_by": { "name": "Dockflow" }
},
"shipment": { "id": 74123, "status": "SAILING" }
}
shipment is the full object that GET /ocean/shipments/{id} returns; the example shortens it. The event id is the same for a redelivery of the same body. Retries, timeouts and ordering follow Webhook delivery.
| Header | Value |
|---|---|
X-Shipsgo-Webhook-Id | The id of the destination. |
X-Shipsgo-Webhook-Name | The slug of the destination, not its display name. |
X-Shipsgo-Webhook-Signature | HMAC-SHA256 of the body with your signing secret, as bare hex. |
X-Dockflow-Signature | The same value as sha256=<hex>. |
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 = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)
Pass the value of X-Shipsgo-Webhook-Signature as header. For X-Dockflow-Signature, compare against "sha256=" + expected. An existing ShipsGo verification keeps working when it computes the same bare hex value.
How to test
- Create a key with the UAT environment and the Downstream scope. It works on
https://uat.dockflow.com/compat/shipsgo/v2and writes to your UAT organisation. A real number is really tracked there. - Create a shipment:
curl -X POST https://uat.dockflow.com/compat/shipsgo/v2/ocean/shipments \
-H "X-Shipsgo-User-Token: uat_acme-logistics_Zk3n1c0Q2m9..." \
-H "Content-Type: application/json" \
-d '{"reference":"PO-4564268","booking_number":"94512540"}'
- Read it with the
idfrom the answer. The import runs after the answer, sostatusstarts atNEW.
curl https://uat.dockflow.com/compat/shipsgo/v2/ocean/shipments/74123 \
-H "X-Shipsgo-User-Token: uat_acme-logistics_Zk3n1c0Q2m9..."
- 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=shipsgo-v2 with an Upstream key returns these same shipment objects. See Upstream filters.