🔼 Upstream API
Base URL:
https://api.dockflow.com/v3Authentication: an API key with theupstreamscope,prod_…onapi.dockflow.comoruat_…onuat.dockflow.com. See Authorization.
1 · Delivery options
| Mode | Workflow | Best for |
|---|---|---|
| Polling | GET /upstream → a page of up to 1000 tradeflows, plus next_cursor for the next page. | Batch jobs, cron. |
| Webhooks | Dockflow 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_pagecounts tradeflows, so a page holding split shipments contains more records thanper_page.next_cursor- opaque string. Non-null when the page holdsper_pagetradeflows and more may follow.nullwhen 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:
- First call of a cycle:
GET /upstream?since=T(whereTis your last saved watermark, see step 4). - If
next_cursoris non-null: call again with bothsince=Tandcursor=<next_cursor>, keepingsince=Tidentical across every call in this cycle. Repeat this step with each newnext_cursoruntilnext_cursorisnull. - When
next_cursorisnull: you have drained the cycle. Stop. - Persist watermark: scan every tradeflow received across all pages of this cycle, take the highest
metadata.last_updated, and save it as your newTfor 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_cursoris non-null, you have more data. Treating it as optional skips records. - Advancing
sincebetween pages. This resets the pagination window and causes skips/duplicates. Keepsincefixed for the full cycle. - Using
next_cursoracross 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,failsafeandper_pageis part of it. Keep the filters identical on every call: a different combination of filters gets a position of its own. sinceonly seeds the first call. Withoutsince, the first call starts from the default 15-minute window. From the second call on,sinceis ignored.- Each call continues where the last response ended. A response never holds more than
per_pagetradeflows. A non-nullnext_cursormeans 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_cursoryou 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 poll | Meaning |
|---|---|
| Acked | The next call continued where this response ended. |
| Overlap | The next call asked for part of this response again. |
| Asked again | The next call sent the same since again. |
| Gap | The next call started after where this response ended. The tradeflows in between were never asked for, and the console shows how many. |
| Concurrent | The next call started before this one had finished. |
| No ack | The 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
- In the API Console, open API Keys › Webhooks and add a destination.
- Enter a name and the URL. The URL must use
httpsand must not contain a username or password. - 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,ExpectandUpgradeare set by Dockflow and cannot be overridden. - 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.
- Choose the delivery mode:
| Mode | What happens |
|---|---|
| Dry run | Every 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. |
| Live | Updates are delivered to your endpoint. |
| Off | Nothing 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 nonext_cursor. - The record is the same as polling returns with the default
fields(events,locations,containers,shipment).fieldscannot be changed for webhooks, sovesselsis not included. - A tradeflow is sent whenever its
metadata.last_updatedmoves. The whole record is sent each time, not a diff.
Responses, retries and ordering
| Your endpoint returns | Dockflow does |
|---|---|
2xx | Delivered. |
4xx (incl. 401/403) | Treated as a refusal. Not retried; delivery moves on to the next update. |
5xx, timeout (30 s), connection error | Retried with back‑off, up to 5 attempts in total. |
- Respond within 30 seconds. Redirects are not followed: a
3xxis 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 UATocean_basicmock 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).
| Field | Window |
|---|---|
demurrage_free_time_expires | Free time in port (demurrage), from discharge |
detention_free_time_expires | Free time out of port (detention), from gate‑out; before gate‑out it is projected after demurrage/storage |
combined_free_time_expires | Combined demurrage + detention. When a combined rule applies, the two fields above carry this same value |
storage_free_time_expires | Terminal 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.
| Field | Description |
|---|---|
type | Normalised cut‑off type, see below |
raw_code / raw_name | The code and name as the carrier published them |
cargo_type | Cargo type the deadline applies to, when the carrier distinguishes one |
carrier | SCAC of the carrier that published the deadline |
operating_carrier | SCAC of the vessel operator |
is_cross_published | true when the deadline comes from another carrier on the same sailing because your carrier published none of that type |
at | The deadline (ISO 8601) |
Cut‑off types (the first five follow the DCSA Booking 2.0 cutOffDateTimeCode codes):
type | Deadline |
|---|---|
DCO | Documentation cut‑off |
VCO | VGM cut‑off |
FCO | FCL delivery cut‑off (yard cut‑off) |
LCO | LCL delivery cut‑off |
EFC | Earliest full‑container delivery (gate opening) |
SHI | Shipping instructions cut‑off |
DGD | Dangerous goods declaration cut‑off |
CUSTOMS | Customs cut‑off |
OOG | Out‑of‑gauge cargo cut‑off |
ECP | Latest empty container pick‑up |
ECP_EARLIEST | Earliest 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_actualistruewhen the resolved port of loading is backed by a real, dated, actual vessel‑departure (VD) or export‑load (AE) event.arrival_is_actualistruewhen 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 vesselis_final_pod: Final Port of Discharge — where the container will be unloaded from the final vesselis_transshipment: Transshipment Port — intermediate port where cargo transfers between vesselsis_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 Status | Current Vessel |
|---|---|
| Waiting at POL (no ATD yet) | First vessel |
| In transit on a leg | Vessel of that leg |
| On transshipment quay (arrived at T/S, waiting for next vessel) | null |
| Arrived at final POD | Last vessel |
| Field | Type | Description |
|---|---|---|
name | string | Vessel name |
imo | string | null | IMO number |
mmsi | string | null | MMSI number |
position | object | null | Live 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.
| Field | Type | Description |
|---|---|---|
lat | number | Latitude in decimal degrees (WGS84) |
lng | number | Longitude in decimal degrees (WGS84) |
timestamp | string | ISO 8601 UTC time of the AIS fix |
source | string | Always "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:
| Field | Type | Description |
|---|---|---|
distance_km | float | Total route distance in kilometers (Haversine calculation) |
co2_emissions_kg | float | Total CO2 emissions in kg (Well-to-Wheel = TTW + WTT) |
ttw_kg | float | Tank-to-Wheel emissions (direct fuel combustion) |
wtt_kg | float | Well-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
| Field | Type | Description |
|---|---|---|
commercial | string | null | Shipping line release state, or null when no reading exists for this light. See Status values below. |
commercial_color | string | null | GREEN, RED, or null when the state has no color mapping. |
commercial_latest_update | string | null | ISO 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. |
customs | string | null | Customs clearance state, or null when no reading exists for this light. See Status values below. |
customs_color | string | null | GREEN, BLUE, ORANGE, RED, or null when the state has no color mapping. |
customs_latest_update | string | null | ISO 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 | string | null | Terminal ready state, or null when no reading exists for this light. See Status values below. |
terminal_color | string | null | GREEN, RED, or null when the state has no color mapping. |
terminal_latest_update | string | null | ISO 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_code | string | null | UNLOCODE of the terminal (e.g., BEANR) — taken from the most recent reading across the three lights. |
source | string | null | Upstream 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 | *_color | Description |
|---|---|---|
RELEASED | GREEN | Released / cleared / ready |
BLOCKED | RED | Blocked or otherwise not released |
UNKNOWN | null | A reading exists but its upstream state could not be classified as released or blocked |
null | null | No reading has arrived for this light yet (the slot is absent) |
The customs light can additionally report two non-blocking intermediate states:
| Value | customs_color | Description |
|---|---|---|
PRE_LODGE | BLUE | Customs pre-lodge / partially cleared — indicative |
PARTIAL | ORANGE | Non-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:
| Field | Type | Description |
|---|---|---|
vessel_schedule_event_date_zulu | string | null | Consensus date in UTC (ISO 8601) |
vessel_schedule_event_date_local | string | null | Consensus 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_localuses the same timezone asevent_date_local(derived from the port location) - When the tradeflow's own
_is_actualistrue, 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:
| Field | Type | Description |
|---|---|---|
event_date_zulu | string | The same moment in UTC, ISO 8601 with a +00:00 offset |
event_date_local | string | Local time at the location in ISO 8601 format |
timezone_geo | string | IANA timezone identifier (e.g., Europe/Brussels) |
timezone_utc_offset_minutes | integer | UTC offset in minutes (e.g., 120 for UTC+02:00) |
timezone_abbreviation | string | Timezone abbreviation (e.g., CEST, PDT) |
vessel_schedule_event_date_zulu | string | omitted | Vessel schedule consensus date in UTC (see §7) |
vessel_schedule_event_date_local | string | omitted | Vessel 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
| Code | Category | Description |
|---|---|---|
| GE | MILESTONE | Empty equipment dispatched |
| GN | MILESTONE | Gate in |
| AE | MILESTONE | Loaded on vessel |
| VD | MILESTONE | Vessel departure |
| VA | MILESTONE | Vessel arrival |
| UV | MILESTONE | Unloaded from vessel |
| GT | MILESTONE | Gate out |
| GD | MILESTONE | Gate out to an inland depot |
| GR | MILESTONE | Empty 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
| Param | What it does |
|---|---|
since=ISO‑date | Only 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=REF | Filter by your tradeflow reference. |
container=REF | Filter by container number. |
category=MILESTONE | Keep only events of this category: MILESTONE, SHIPMENT or LOCATION. Every event is currently a MILESTONE. |
code=VD | Keep only events whose code starts with this value. One of GE, GN, AE, VD, VA, UV, GT, GD, GR, POS. |
active | Filter 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‑1000 | Tradeflows 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
| Status | Meaning | Suggested fix |
|---|---|---|
| 400 | Invalid 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 / 403 | Missing, unknown, revoked or wrong‑environment API key. | See authentication errors. |
| 5xx | Dockflow outage. | Retry with back‑off. |
Questions? Contact [email protected] — engineers respond within one business day.