Skip to main content

🔼 Upstream API

Base URL: https://api.dockflow.com/v3 Authentication: an API key with the upstream scope, prod_… on api.dockflow.com or uat_… on uat.dockflow.com. See Authorization.


1 · Delivery options​

ModeWorkflowBest for
PollingGET /upstream → a page of up to 1000 tradeflows, plus next_cursor for the next page.Batch jobs, cron.
WebhooksDockflow POSTs each changed tradeflow to your HTTPS endpoint, about a minute after it changes (see Webhook delivery).Real‑time pipelines.

Both modes return the same record (§2). A tradeflow produces one record per shipment: a split shipment yields several records that share a tradeflow_reference and differ in shipment.split_shipment_number.

Polling response envelope​

Every polling response is wrapped in this top-level envelope:

{
"data": [ /* array of tradeflow objects, see §2 */ ],
"next_cursor": "eyJjaGFuZ2VkX2F0IjoiMjAyNS0wNS0xMFQxMjoyMjo0OCswMDowMCIsImlkIjoxMjM0NX0="
}
  • data - the records for the page of tradeflows matching your filters. per_page counts tradeflows, so a page holding split shipments contains more records than per_page.
  • next_cursor - opaque string. Non-null when the page holds per_page tradeflows and more may follow. null when you have reached the end of the result set.

Default window. Without since, cursor, tradeflow or container, a call returns only the tradeflows that changed in the last 15 minutes. With any of those and no since, there is no time window.

Polling flow (since + cursor combined)​

since and cursor are not alternatives - they are combined in the same query and must be used together when paginating. The correct polling loop is:

  1. First call of a cycle: GET /upstream?since=T (where T is your last saved watermark, see step 4).
  2. If next_cursor is non-null: call again with both since=T and cursor=<next_cursor>, keeping since=T identical across every call in this cycle. Repeat this step with each new next_cursor until next_cursor is null.
  3. When next_cursor is null: you have drained the cycle. Stop.
  4. Persist watermark: scan every tradeflow received across all pages of this cycle, take the highest metadata.last_updated, and save it as your new T for the next polling cycle.

The since watermark must not change mid-cycle. Only advance it after the cursor loop has completed.

Why the cursor is required (ties on identical timestamps)​

Results are ordered by (upstream_changed_at, id). Two or more tradeflows can share the exact same upstream_changed_at down to the microsecond, especially during bulk imports or batch updates. The since filter alone cannot distinguish between tradeflows at the boundary timestamp, so paginating with since only would either skip or duplicate rows at that boundary.

The cursor carries both changed_at and the tradeflow id as a tiebreaker, guaranteeing that every tradeflow is returned exactly once across the full cycle.

since is compared with the tradeflow's change timestamp, the value returned as metadata.last_updated, and is exclusive: a tradeflow whose last_updated equals since is not returned again.

Common mistakes​

  • Dropping the cursor after the first page. If next_cursor is non-null, you have more data. Treating it as optional skips records.
  • Advancing since between pages. This resets the pagination window and causes skips/duplicates. Keep since fixed for the full cycle.
  • Using next_cursor across polling cycles. Cursors are only valid within a single cycle. When starting a new cycle, go back to step 1 with the new watermark.
  • Treating the cursor as parseable. It is opaque base64-encoded state. Do not decode or construct it yourself.

Failsafe polling (position kept by Dockflow)​

Add failsafe=1 and Dockflow keeps your position instead of you. Send the same request on every poll, and nothing is skipped, also after maintenance or downtime on your side.

GET /upstream?failsafe=1&since=2025-05-10T12:00:00Z
  • One position per API key and per set of filters. Every query parameter except since, cursor, failsafe and per_page is part of it. Keep the filters identical on every call: a different combination of filters gets a position of its own.
  • since only seeds the first call. Without since, the first call starts from the default 15-minute window. From the second call on, since is ignored.
  • Each call continues where the last response ended. A response never holds more than per_page tradeflows. A non-null next_cursor means more are waiting: call again with the same request, with or without the cursor. Skipping a page loses nothing.
  • A cursor you send is served. Pass a next_cursor you received earlier and that page is sent again, also when your position is already further.
  • A slow response is sent again. When a response takes so long that it may not have reached you, your position does not move, and the next call returns the same tradeflows.

To reset a position, contact [email protected].

Poll history​

The API Console lists every call made with your keys to the v3 upstream over the last 30 days: what it asked for, how many tradeflows it got and where the response ended. Each call also shows what your next call said about it:

Next pollMeaning
AckedThe next call continued where this response ended.
OverlapThe next call asked for part of this response again.
Asked againThe next call sent the same since again.
GapThe next call started after where this response ended. The tradeflows in between were never asked for, and the console shows how many.
ConcurrentThe next call started before this one had finished.
No ackThe next call sent neither since nor cursor.

A gap almost always means a full page whose next_cursor was not followed.

Webhook delivery​

Instead of polling, Dockflow can POST every changed tradeflow to your endpoint.

Set up a destination​

  1. In the API Console, open API Keys › Webhooks and add a destination.
  2. Enter a name and the URL. The URL must use https and must not contain a username or password.
  3. Add the headers your endpoint needs to authenticate us, e.g. Authorization: Bearer …. Up to 10 headers; Host, Content-Length, Content-Type, Transfer-Encoding, Connection, Expect and Upgrade are set by Dockflow and cannot be overridden.
  4. Optionally set Hold updates for (0–1440 minutes): only changes at least that old are sent, so a tradeflow that is still being updated settles before it is delivered.
  5. Choose the delivery mode:
ModeWhat happens
Dry runEvery update is built and recorded in the console, but nothing is sent. Use it to inspect what your endpoint would receive. New destinations start here.
LiveUpdates are delivered to your endpoint.
OffNothing is built, sent or recorded.

You can add several destinations, e.g. a staging and a production endpoint. A UAT destination belongs on your UAT organisation.

Request​

POST /your/endpoint HTTP/1.1
Content-Type: application/json
Authorization: Bearer <the header value you configured>

{"data":[ { /* one record, §2 */ } ]}
  • Each request carries exactly one record in data: one shipment of one tradeflow. There is no next_cursor.
  • The record is the same as polling returns with the default fields (events, locations, containers, shipment). fields cannot be changed for webhooks, so vessels is not included.
  • A tradeflow is sent whenever its metadata.last_updated moves. The whole record is sent each time, not a diff.

Responses, retries and ordering​

Your endpoint returnsDockflow does
2xxDelivered.
4xx (incl. 401/403)Treated as a refusal. Not retried; delivery moves on to the next update.
5xx, timeout (30 s), connection errorRetried with back‑off, up to 5 attempts in total.
  • Respond within 30 seconds. Redirects are not followed: a 3xx is a failed delivery.
  • Delivery is at least once: you can receive the same record more than once. Records can also arrive out of order. Deduplicate and order on (tradeflow_reference, shipment.split_shipment_number, metadata.last_updated) and ignore a record older than the one you already stored.
  • Requests are not signed. Protect your endpoint with the headers you configure (a secret token), and contact [email protected] if you also need to allowlist our IP addresses.

Try it before you have shipments​

Open the destination in API Keys › Webhooks and click Send test. Dockflow sends one example record to your endpoint right away, whatever the delivery mode (Dry run and Off included), so you can build and debug your handler without real tradeflows.

  • Scenario: Ocean basic: one container that left Antwerp (BEANR) yesterday and is due in Los Angeles (USLAX) in 30 days. Gate‑out empty (GE), gate‑in (GN) and loading (AE) are actual; vessel arrival (VA), discharge (UV), gate‑out (GT) and empty return (GR) are estimates. It is the same story as the UAT ocean_basic mock tracking scenario. Dates are relative to the moment you send.
  • Tradeflow reference (optional): replaces the placeholder DOCKFLOW-TEST, so you can test matching an update to an order in your own system.
  • A test request is identical to a real one except for the extra header X-Dockflow-Test: true. Filter on it if your endpoint must not act on test data.
  • A test send is a single attempt: it is not retried. The result appears in the delivery history, marked Test.

Resend a call​

Expand any call in the delivery history and click Resend this body to send exactly the same bytes again, to the destination's current URL and headers, whatever its delivery mode. This is useful after fixing a bug in your handler, or to replay a Dry run body for real. A resend is a single attempt and is marked Resend. Bodies are kept for 30 days; an older call can no longer be resent.

Delivery history​

Each destination in API Keys › Webhooks lists every call Dockflow made: tradeflow, containers, attempt number, result, HTTP status, duration and the start of your response body. You can open the exact body that was sent. Bodies are kept for 30 days.


2 · Envelope format​

{
"version": "3.0.0",
"tradeflow_reference": "PO4564268",

"bill_of_lading": "ANR00001010", // primary bill of lading (null if none)
"booking": "94512540", // primary booking number (null if none)
"additional_references": { // any further references beyond the primary
"bill_of_lading": ["ANR00001011"], // remaining bills of lading (empty if none)
"booking": [] // remaining booking numbers (empty if none)
},

"data_quality": { // indicative; freshness_min, missed_milestones and has_live_data are fixed values today
"freshness_min": 12,
"anomalies": 0,
"missed_milestones": 1,
"has_live_data": true,
"is_singular_shipment": false
},

"metadata": {
"last_updated": "2025‑05‑10T12:22:48+00:00",
"carrier_name": "Maersk",
"carrier_scac": "MAEU",
"is_active": true // whether the tradeflow is currently active
},

"locations": [
{ "reference": "BEANR", "name": "BE Antwerpen", "type": "Port",
"timezone": "Europe/Brussels", "country": "Belgium",
"lat": 51.263, "lng": 4.398 }
],

"containers": [
{ "reference": "ABCD1234567", // container number (BIC code)
"iso_type": "45R1", // ISO 6346 size-type code; null if carrier did not provide one
"iso_type_group": "45RT", // ISO 6346 type group for iso_type; groups equivalent size-type codes together (45G1 and 45G0 both report as 45GP). Null when iso_type is unknown
"length_ft": 40, // container length in feet, derived from iso_type
"is_reefer": true, // true if the iso_type identifies a reefer
"free_time": { // detention & demurrage windows; null when no free-time rules apply
"demurrage_free_time_expires": "2025‑06‑08T23:59:59+02:00",
"detention_free_time_expires": "2025‑06‑15T23:59:59+02:00",
"combined_free_time_expires": null, // non-null when carrier bundles D&D into one window (e.g. Evergreen); demurrage_/detention_ then mirror this value
"storage_free_time_expires": null, // terminal storage free-time expiry, independent of carrier D&D
},
"import_release_status": { // terminal release lights (Antwerp only)
"commercial": "RELEASED", // shipping line release
"commercial_color": "GREEN",
"commercial_latest_update": "2025-06-05T10:30:00+02:00",
"customs": "RELEASED", // customs clearance
"customs_color": "GREEN",
"customs_latest_update": "2025-06-05T11:00:00+02:00",
"terminal": "RELEASED", // terminal ready for pickup
"terminal_color": "GREEN",
"terminal_latest_update": "2025-06-05T11:15:00+02:00",
"terminal_code": "BEANR",
"source": "nxtport_cpu"
},
"arrival_at_pod_event_date": "2025‑06‑06T15:30:00‑07:00", // container ETA/ATA at final POD
"arrival_at_pod_event_date_extended": {
"event_date_zulu": "2025-06-06T22:30:00+00:00",
"event_date_local": "2025-06-06T15:30:00-07:00",
"timezone_geo": "America/Los_Angeles",
"timezone_utc_offset_minutes": -420,
"timezone_abbreviation": "PDT",
"vessel_schedule_event_date_zulu": "2025-06-06T22:30:00+00:00",
"vessel_schedule_event_date_local": "2025-06-06T15:30:00-07:00"
},
"arrival_at_pod_is_actual": false, // true if actual, false if estimated
"latest_actual_milestone": { // last confirmed milestone for this container
"code": "GN",
"event_date": "2025‑05‑09T10:34:00+02:00",
"event_date_extended": {
"event_date_zulu": "2025-05-09T08:34:00+00:00",
"event_date_local": "2025-05-09T10:34:00+02:00",
"timezone_geo": "Europe/Brussels",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST"
},
"location_reference": "BEANR",
"is_pol": false, // true if at port of loading
"is_final_pod": false, // true if at final port of discharge
"is_transshipment": false, // true if at a transshipment port
"is_prepol": false, // true if before port of loading
"is_postpod": false // true if after final port of discharge
}}
],

"vessels": [
{ "name": "MAERSK SOPHIE", "imo": "9721234", "mmsi": "219028000" }
],

"shipment": {
"split_shipment_number": 2, // omitted for the first (or only) shipment
"port_of_loading": "BEANR",
"departure_event_date": "2025‑05‑14T10:00:00+02:00",
"departure_event_date_extended": {
"event_date_zulu": "2025-05-14T08:00:00+00:00",
"event_date_local": "2025-05-14T10:00:00+02:00",
"timezone_geo": "Europe/Brussels",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST",
"vessel_schedule_event_date_zulu": "2025-05-14T08:00:00+00:00",
"vessel_schedule_event_date_local": "2025-05-14T10:00:00+02:00"
},
"departure_is_actual": true,

"port_of_discharge": "USLAX",
"arrival_event_date": "2025‑06‑06T15:30:00‑07:00",
"arrival_event_date_extended": {
"event_date_zulu": "2025-06-06T22:30:00+00:00",
"event_date_local": "2025-06-06T15:30:00-07:00",
"timezone_geo": "America/Los_Angeles",
"timezone_utc_offset_minutes": -420,
"timezone_abbreviation": "PDT",
"vessel_schedule_event_date_zulu": "2025-06-06T22:30:00+00:00",
"vessel_schedule_event_date_local": "2025-06-06T15:30:00-07:00"
},
"arrival_is_actual": false,

"transshipment_ports": ["ESBCN"],
"incoterms": "FOB",
"cargo_opening": "2025‑05‑11T08:00:00+02:00", // earliest moment cargo can be dropped at POL
"cutoffs": [ // carrier deadlines at the port of loading, see below
{ "type": "VCO", "raw_code": "VGM", "raw_name": "VGM cut-off",
"cargo_type": "ALL", "carrier": "MAEU", "operating_carrier": "MAEU",
"is_cross_published": false, "at": "2025-05-12T12:00:00+00:00" }
],
"shipment_legs": [
{
"sequence_number": 0,
"vessel": "MAERSK SOPHIE",

"departure": "BEANR",
"departure_event_date": "2025-05-14T10:00:00+02:00",
"departure_event_date_extended": {
"event_date_zulu": "2025-05-14T08:00:00+00:00",
"event_date_local": "2025-05-14T10:00:00+02:00",
"timezone_geo": "Europe/Brussels",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST",
"vessel_schedule_event_date_zulu": "2025-05-14T08:00:00+00:00",
"vessel_schedule_event_date_local": "2025-05-14T10:00:00+02:00"
},
"departure_is_actual": true,
"departure_location_type": "pol",

"arrival": "ESBCN",
"arrival_event_date": "2025-05-20T08:00:00+02:00",
"arrival_event_date_extended": {
"event_date_zulu": "2025-05-20T06:00:00+00:00",
"event_date_local": "2025-05-20T08:00:00+02:00",
"timezone_geo": "Europe/Madrid",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST",
"vessel_schedule_event_date_zulu": "2025-05-20T06:00:00+00:00",
"vessel_schedule_event_date_local": "2025-05-20T08:00:00+02:00"
},
"arrival_is_actual": false,
"arrival_location_type": "transshipment"
},
{
"sequence_number": 1,
"vessel": "MAERSK SOPHIE",

"departure": "ESBCN",
"departure_event_date": "2025-05-21T14:00:00+02:00",
"departure_event_date_extended": {
"event_date_zulu": "2025-05-21T12:00:00+00:00",
"event_date_local": "2025-05-21T14:00:00+02:00",
"timezone_geo": "Europe/Madrid",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST",
"vessel_schedule_event_date_zulu": "2025-05-21T12:00:00+00:00",
"vessel_schedule_event_date_local": "2025-05-21T14:00:00+02:00"
},
"departure_is_actual": false,
"departure_location_type": "transshipment",

"arrival": "USLAX",
"arrival_event_date": "2025-06-06T15:30:00-07:00",
"arrival_event_date_extended": {
"event_date_zulu": "2025-06-06T22:30:00+00:00",
"event_date_local": "2025-06-06T15:30:00-07:00",
"timezone_geo": "America/Los_Angeles",
"timezone_utc_offset_minutes": -420,
"timezone_abbreviation": "PDT",
"vessel_schedule_event_date_zulu": "2025-06-06T22:30:00+00:00",
"vessel_schedule_event_date_local": "2025-06-06T15:30:00-07:00"
},
"arrival_is_actual": false,
"arrival_location_type": "pod"
}
],

"current_vessel": { // vessel the shipment is currently on
"name": "MAERSK SOPHIE",
"imo": "9721234",
"mmsi": "219028000",
"position": { // live AIS position; null unless in transit (between ATD and ATA)
"lat": 51.2546,
"lng": 4.4025,
"timestamp": "2025-06-04T08:12:00+00:00", // ISO 8601, refreshed at least hourly
"source": "ais"
}
},

"carbon_emissions": { // CO2 emissions (null if not yet calculated)
"distance_km": 12450.5, // route distance in kilometers
"co2_emissions_kg": 2134.8, // total CO2 (Well-to-Wheel)
"ttw_kg": 1850.2, // Tank-to-Wheel (direct combustion)
"wtt_kg": 284.6 // Well-to-Tank (fuel production)
}
},

"events": [
{ "id": "evt_01", "category": "MILESTONE", "code": "GN",
"message": "Gate in",
"event_date": "2025‑05‑09T10:34:00+02:00",
"event_date_extended": {
"event_date_zulu": "2025-05-09T08:34:00+00:00",
"event_date_local": "2025-05-09T10:34:00+02:00",
"timezone_geo": "Europe/Brussels",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST"
},
"actual": true,
"container_reference": "ABCD1234567",
"location_reference": "BEANR",
"is_pol": false,
"is_final_pod": false,
"is_transshipment": false,
"is_prepol": false,
"is_postpod": false }
]
}

Time zones. Plain date‑time fields such as event_date, departure_event_date, arrival_event_date and arrival_at_pod_event_date are ISO 8601 with an explicit offset, normalised to UTC (+00:00). For the local time at the location, read event_date_local in the matching *_extended object (§8). Free‑time expiries are expressed in the timezone of the location where free time starts. Always parse the offset rather than assuming one.

Free time​

free_time gives the end of each free‑time window for the container, or is null when no free‑time rules are configured for it. Each expiry is the last second of the final free day; a field is null when that window does not apply or has not started (e.g. no discharge yet).

FieldWindow
demurrage_free_time_expiresFree time in port (demurrage), from discharge
detention_free_time_expiresFree time out of port (detention), from gate‑out; before gate‑out it is projected after demurrage/storage
combined_free_time_expiresCombined demurrage + detention. When a combined rule applies, the two fields above carry this same value
storage_free_time_expiresTerminal storage free time, from discharge

Cut‑offs​

shipment.cutoffs lists the carrier‑published deadlines for the port of loading, earliest first, or [] when none are known. These are the closing times the Dockflow app shows as Closings.

FieldDescription
typeNormalised cut‑off type, see below
raw_code / raw_nameThe code and name as the carrier published them
cargo_typeCargo type the deadline applies to, when the carrier distinguishes one
carrierSCAC of the carrier that published the deadline
operating_carrierSCAC of the vessel operator
is_cross_publishedtrue when the deadline comes from another carrier on the same sailing because your carrier published none of that type
atThe deadline (ISO 8601)

Cut‑off types (the first five follow the DCSA Booking 2.0 cutOffDateTimeCode codes):

typeDeadline
DCODocumentation cut‑off
VCOVGM cut‑off
FCOFCL delivery cut‑off (yard cut‑off)
LCOLCL delivery cut‑off
EFCEarliest full‑container delivery (gate opening)
SHIShipping instructions cut‑off
DGDDangerous goods declaration cut‑off
CUSTOMSCustoms cut‑off
OOGOut‑of‑gauge cargo cut‑off
ECPLatest empty container pick‑up
ECP_EARLIESTEarliest empty container pick‑up

cargo_opening is when the terminal opens its gate for the sailing. It can differ from a carrier's earliest‑acceptance cut‑off.

Bills of lading & booking numbers​

A tradeflow usually has a single bill of lading and booking number, surfaced as the top‑level bill_of_lading and booking strings (the primary reference). When a tradeflow carries more than one, the extras are listed in additional_references.bill_of_lading / additional_references.booking.

The primary is the oldest reference (earliest event date) of each type — the same one the Dockflow UI shows first. These references are tradeflow‑scoped, so on a split shipment they repeat identically across each shipment entry. Either value is null (and the corresponding additional array empty) when the tradeflow has no reference of that type yet.

Actual vs estimated departure / arrival​

departure_is_actual and arrival_is_actual (at shipment level, and the matching per‑leg keys on each shipment_leg) report whether the current transport plan considers the departure, respectively the arrival, actually to have happened:

  • departure_is_actual is true when the resolved port of loading is backed by a real, dated, actual vessel‑departure (VD) or export‑load (AE) event.
  • arrival_is_actual is true when the final port of discharge is backed by a confirmed arrival reading and the departure was itself actual.

These fields reflect the current view of the transport plan, not a frozen per‑message snapshot. The plan is recomputed as new statuses arrive, so a value may legitimately change between responses (including true → false → true) while the plan is refined around a real departure or arrival — most commonly when an estimated booking stub for the first port of loading is later superseded by the actual departure. Treat them as the latest best answer and re‑read the shipment on updates rather than treating a single response as permanent.


3 · Location type flags​

Both the latest_actual_milestone object (in containers) and all events in the events array include boolean flags indicating the type of location where the milestone occurred:

  • is_pol: Port of Loading — where the container was loaded onto the first vessel
  • is_final_pod: Final Port of Discharge — where the container will be unloaded from the final vessel
  • is_transshipment: Transshipment Port — intermediate port where cargo transfers between vessels
  • is_prepol: Pre-POL Location — locations before the port of loading (e.g., depot, rail terminal)
  • is_postpod: Post-POD Location — locations after final discharge (e.g., inland destination, final delivery point)

Note: Multiple flags can be true simultaneously. For example, a milestone at a transshipment port that also serves as a POL for the next leg could have both is_transshipment and is_pol set to true.

Usage: These flags are particularly useful for vessel arrival/departure events (VA/VD) to determine whether the vessel is arriving at or departing from a POL, POD, or transshipment port.


4 · Current vessel​

The current_vessel field in the shipment object shows which vessel the cargo is currently on:

Shipment StatusCurrent Vessel
Waiting at POL (no ATD yet)First vessel
In transit on a legVessel of that leg
On transshipment quay (arrived at T/S, waiting for next vessel)null
Arrived at final PODLast vessel
FieldTypeDescription
namestringVessel name
imostring | nullIMO number
mmsistring | nullMMSI number
positionobject | nullLive AIS position — see below

Example:

"current_vessel": {
"name": "MAERSK SOPHIE",
"imo": "9721234",
"mmsi": "219028000",
"position": {
"lat": 51.2546,
"lng": 4.4025,
"timestamp": "2025-06-04T08:12:00+00:00",
"source": "ais"
}
}

Returns null (the whole object) when cargo is on a transshipment quay between vessels.

4.1 · Vessel position​

current_vessel.position exposes the live AIS-derived position of the current vessel. It is populated only while the shipment is in transit on a sea leg — i.e. between the actual departure (ATD) from the leg's POL and the actual arrival (ATA) at the leg's POD. In all other states (waiting at POL, arrived at final POD), position is null while current_vessel itself stays set.

FieldTypeDescription
latnumberLatitude in decimal degrees (WGS84)
lngnumberLongitude in decimal degrees (WGS84)
timestampstringISO 8601 UTC time of the AIS fix
sourcestringAlways "ais" for now; reserved for future provenance values

Update cadence: the underlying AIS feed is refreshed at least hourly; consumers should treat timestamp as the source of truth for freshness rather than the poll time.

position is null when:

  • the shipment is not currently in transit on a sea leg, or
  • the vessel has no AIS-linked devices, or
  • no AIS reading has been received yet for the vessel.

position holds the vessel's latest AIS position (lat, lng, timestamp, source: "ais") while the cargo is under way on that vessel. It is null before departure, after arrival, and when no valid position is known.


5 · Carbon emissions​

The carbon_emissions field provides CO2 emission data for the shipment route:

FieldTypeDescription
distance_kmfloatTotal route distance in kilometers (Haversine calculation)
co2_emissions_kgfloatTotal CO2 emissions in kg (Well-to-Wheel = TTW + WTT)
ttw_kgfloatTank-to-Wheel emissions (direct fuel combustion)
wtt_kgfloatWell-to-Tank emissions (upstream fuel production)

Example:

"carbon_emissions": {
"distance_km": 12450.5,
"co2_emissions_kg": 2134.8,
"ttw_kg": 1850.2,
"wtt_kg": 284.6
}

Note: Returns null if emissions have not yet been calculated (calculation typically occurs after shipment arrival).


6 · Import release status (green lights)​

The import_release_status field in each container provides real-time pickup authorization status from terminal systems. This data comes from NxtPort Certified Pickup (CPu) for containers at Antwerp-Bruges terminals.

Fields​

FieldTypeDescription
commercialstring | nullShipping line release state, or null when no reading exists for this light. See Status values below.
commercial_colorstring | nullGREEN, RED, or null when the state has no color mapping.
commercial_latest_updatestring | nullISO 8601 event date of the current reading, or null when the light is absent or the reading carries no date. Independent of the state value.
customsstring | nullCustoms clearance state, or null when no reading exists for this light. See Status values below.
customs_colorstring | nullGREEN, BLUE, ORANGE, RED, or null when the state has no color mapping.
customs_latest_updatestring | nullISO 8601 event date of the current reading, or null when the light is absent or the reading carries no date. Independent of the state value.
terminalstring | nullTerminal ready state, or null when no reading exists for this light. See Status values below.
terminal_colorstring | nullGREEN, RED, or null when the state has no color mapping.
terminal_latest_updatestring | nullISO 8601 event date of the current reading, or null when the light is absent or the reading carries no date. Independent of the state value.
terminal_codestring | nullUNLOCODE of the terminal (e.g., BEANR) — taken from the most recent reading across the three lights.
sourcestring | nullUpstream data source (e.g., nxtport_cpu) — taken from the most recent reading across the three lights.

Status values​

The same set of values applies to all three lights:

Value*_colorDescription
RELEASEDGREENReleased / cleared / ready
BLOCKEDREDBlocked or otherwise not released
UNKNOWNnullA reading exists but its upstream state could not be classified as released or blocked
nullnullNo reading has arrived for this light yet (the slot is absent)

The customs light can additionally report two non-blocking intermediate states:

Valuecustoms_colorDescription
PRE_LODGEBLUECustoms pre-lodge / partially cleared — indicative
PARTIALORANGENon-blocking customs control — FAVV (food safety) or transhipment

Neither PRE_LODGE nor PARTIAL is green, so the container is not yet ready for pickup while either is shown.

Pickup authorization​

Container can be picked up when all three lights are GREEN (RELEASED):

commercial = RELEASED  (GREEN)
customs = RELEASED (GREEN)
terminal = RELEASED (GREEN)
─────────────────────────────────────────────
→ Container ready for pickup ✅

If any light is RED or null — or the customs light is BLUE or ORANGE — the container is not yet ready for pickup.

Example​

"import_release_status": {
"commercial": "RELEASED",
"commercial_color": "GREEN",
"commercial_latest_update": "2025-06-05T10:30:00+02:00",

"customs": "PARTIAL",
"customs_color": "ORANGE",
"customs_latest_update": "2025-06-05T11:00:00+02:00",

"terminal": "RELEASED",
"terminal_color": "GREEN",
"terminal_latest_update": "2025-06-05T11:15:00+02:00",

"terminal_code": "BEANR",
"source": "nxtport_cpu"
}

The three light keys (commercial, customs, terminal) are always present in the object. When a light has not received any reading yet, its value, *_color, and *_latest_update are all null. A light whose reading exists but could not be classified has the value "UNKNOWN" (with *_color null); its *_latest_update may still carry a timestamp. The whole import_release_status block is null only when zero readings exist across all three lights for the container.

Availability​

  • Supported terminals: Antwerp-Bruges (BEANR, BEZEE) via NxtPort CPu
  • Requirements: Your organization must have NxtPort CPu credentials configured in Dockflow
  • Returns null: When Dockflow has no release information for the container (e.g. a non‑Antwerp terminal, or no CPu credentials configured)

7 · Vessel Schedule Intelligence​

Alongside per-tradeflow ETAs/ETDs, the API includes cross-tradeflow consensus dates derived from all tradeflows sharing the same vessel. When multiple tradeflows track the same vessel at the same port, Dockflow aggregates their readings into a single best-estimate using recency-weighted consensus.

Fields​

Vessel schedule data is embedded inside the existing *_extended date objects as two additional fields:

FieldTypeDescription
vessel_schedule_event_date_zulustring | nullConsensus date in UTC (ISO 8601)
vessel_schedule_event_date_localstring | nullConsensus date in local time at the location (ISO 8601)

These fields appear in every _extended object that has vessel schedule context:

  • Shipment level: departure_event_date_extended, arrival_event_date_extended
  • Leg level: each leg's departure_event_date_extended, arrival_event_date_extended
  • Container level: arrival_at_pod_event_date_extended

Example​

"departure_event_date_extended": {
"event_date_zulu": "2025-05-14T08:00:00+00:00",
"event_date_local": "2025-05-14T10:00:00+02:00",
"timezone_geo": "Europe/Brussels",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST",
"vessel_schedule_event_date_zulu": "2025-05-14T08:00:00+00:00", // consensus ETD (UTC)
"vessel_schedule_event_date_local": "2025-05-14T10:00:00+02:00" // consensus ETD (local)
}

Interpretation​

  • Fields are omitted when no vessel schedule data is available (vessel not tracked cross-tradeflow, insufficient data, or low confidence)
  • The vessel_schedule_event_date_local uses the same timezone as event_date_local (derived from the port location)
  • When the tradeflow's own _is_actual is true, the vessel schedule fields are informational only — the actual reading is authoritative

8 · Extended date format​

All date fields (event_date, arrival_at_pod_event_date, departure_event_date, arrival_event_date) include a corresponding *_extended object with full timezone context:

FieldTypeDescription
event_date_zulustringThe same moment in UTC, ISO 8601 with a +00:00 offset
event_date_localstringLocal time at the location in ISO 8601 format
timezone_geostringIANA timezone identifier (e.g., Europe/Brussels)
timezone_utc_offset_minutesintegerUTC offset in minutes (e.g., 120 for UTC+02:00)
timezone_abbreviationstringTimezone abbreviation (e.g., CEST, PDT)
vessel_schedule_event_date_zulustring | omittedVessel schedule consensus date in UTC (see §7)
vessel_schedule_event_date_localstring | omittedVessel schedule consensus date in local time (see §7)

Example:

"event_date": "2025-05-09T10:34:00+02:00",
"event_date_extended": {
"event_date_zulu": "2025-05-09T08:34:00+00:00",
"event_date_local": "2025-05-09T10:34:00+02:00",
"timezone_geo": "Europe/Brussels",
"timezone_utc_offset_minutes": 120,
"timezone_abbreviation": "CEST",
"vessel_schedule_event_date_zulu": "2025-05-09T08:30:00+00:00",
"vessel_schedule_event_date_local": "2025-05-09T10:30:00+02:00"
}

Note: If the location has no timezone configured, timezone_geo, timezone_utc_offset_minutes, and timezone_abbreviation will be null, and event_date_local will match the original event_date. The vessel_schedule_* fields are only present when vessel schedule data is available for that date (see §7).


9 · Status code table​

CodeCategoryDescription
GEMILESTONEEmpty equipment dispatched
GNMILESTONEGate in
AEMILESTONELoaded on vessel
VDMILESTONEVessel departure
VAMILESTONEVessel arrival
UVMILESTONEUnloaded from vessel
GTMILESTONEGate out
GDMILESTONEGate out to an inland depot
GRMILESTONEEmpty equipment returned

10 · Attribute selection​

Choose which blocks to receive with the fields parameter (polling only; webhooks always receive the default set):

fields=events,locations,containers,shipment,vessels

Default fields (when parameter is omitted): events, locations, containers, shipment

To include the vessels array, explicitly add it to your fields parameter. version, tradeflow_reference, the bill of lading and booking fields, data_quality and metadata are always included.


11 · Filters​

ParamWhat it does
since=ISO‑dateOnly tradeflows whose metadata.last_updated is after this timestamp. Include an offset (Z or +00:00). Default: the last 15 minutes, unless cursor, tradeflow or container is given.
cursor=…next_cursor from the previous page (§1).
tradeflow=REFFilter by your tradeflow reference.
container=REFFilter by container number.
category=MILESTONEKeep only events of this category: MILESTONE, SHIPMENT or LOCATION. Every event is currently a MILESTONE.
code=VDKeep only events whose code starts with this value. One of GE, GN, AE, VD, VA, UV, GT, GD, GR, POS.
activeFilter by status: true (strictly active only), false, or both. Default (no param): active, plus deactivated tradeflows that changed in the last 7 days or after your since or cursor, whichever reaches further back.
per_page=1‑1000Tradeflows per page (default 50, max 1000).
fields=…Blocks to include (§10).

category and code filter the events array inside each record; they do not remove tradeflows from the page.

Only your own tradeflows. A partner can add your company to a tradeflow they created (for example by naming you as customer when they push it), and by default that tradeflow is in your feed like any other. If your integration only handles tradeflows you created yourself, enable Only include tradeflows your company created on the API key in the API Console. The setting belongs to the key, not the request: every call made with that key then skips tradeflows another company created.


12 · Error handling​

StatusMeaningSuggested fix
400Invalid parameter (since, per_page, category or code). The body is a JSON string naming the problem, e.g. "per_page must be between 1 and 1000".Fix the parameter.
401 / 403Missing, unknown, revoked or wrong‑environment API key.See authentication errors.
5xxDockflow outage.Retry with back‑off.

Questions? Contact [email protected] — engineers respond within one business day.