The API your systems will use, written by the people who answer the pager.
REST over HTTPS, JSON in and out, ISO 8601 with offsets, UN/LOCODE for places, IMO numbers for vessels. Every terminal gets a sandbox with a copy of its own board.
01Getting started
From a sandbox key to a vessel on a board in about twenty minutes. Everything here works against the sandbox, which is reset to your production berth model every night at 02:00 CET.
- Get a sandbox keyOperations creates it with your terminal; it is a
qs_sk_test_token scoped to one terminal and it cannot write to production. - Confirm your berth model
GET /terminals/{code}/berthsreturns the berths, their LOA limits, depths alongside and crane counts as Quayside has them. If that is wrong, nothing after it is right. - Post a port callA call needs a vessel (IMO), an ETA, an ETD and a berth request. Quayside answers with the call and the rule check it ran.
- Move it and read the conflicts
PATCH /calls/{id}with a new ETA or berth. A 409 with aconflictsarray means the move was refused; a 200 means the board changed. - Subscribe to changesRegister a webhook endpoint for
call.plan.changedso your TOS hears about moves made by planners in the interface, not only by you. - Go to productionSwap the key. The sandbox and production URLs are the same host; the token decides which terminal and which environment you reach.
02Authentication
A bearer token per integration, not per user. Tokens are scoped to one terminal and one environment, and they carry a role: read, plan (may move calls) or admin (may change the berth model). Rotate every 90 days; both keys work during a 24-hour overlap.
# every request curl https://api.quayside.nl/v3/terminals/NLRTM-NOORDKADE/calls \ -H "Authorization: Bearer qs_sk_live_7cA2…" \ -H "Quayside-Version: 2026-08-26"
Pin Quayside-Version to the date you built against. Without it you get the current version, and we do change response shapes — with six months’ notice and a deprecation header, but we do change them.
03Data model
Five objects. A call is one vessel alongside one berth once; everything else hangs off it.
| Object | Key | Belongs to | Notable fields |
|---|---|---|---|
| terminal | UN/LOCODE + slug | — | timezone, tide_station, crane_pool, msw_profile |
| berth | berth_id | terminal | loa_max_m, depth_m, cranes, rail_segment, bollards |
| vessel | IMO | — | name, loa_m, beam_m, draught_m, teu, line, flag |
| call | call_id | terminal, berth, vessel | eta, etd, ata, atd, cranes, status, conflicts[] |
| event | event_id | call | type, reported_at, source, actor, payload |
| document | document_id | call | kind, state, due_at, received_at, party |
Call statuses
A call moves in one direction only. planned → confirmed → alongside → departed, with cancelled reachable from the first two. A call that reached alongside is billable whatever happens next.
04API reference
Twenty-nine endpoints; the eleven that matter are below. Collections are cursor-paginated (?after=, 100 max) and every response carries X-Request-Id, which is the first thing support will ask for.
| Method | Endpoint | Does |
|---|---|---|
| GET | /terminals/{code}/berths | The berth model: limits, depths, cranes, rail segments |
| GET | /terminals/{code}/calls | Calls in a window, filtered by status, berth or line |
| POST | /terminals/{code}/calls | Create a call; runs the rule check before it is saved |
| GET | /calls/{id} | One call with its conflicts, events and documents |
| PATCH | /calls/{id} | Move a call: berth, ETA, ETD, cranes. 409 if refused |
| POST | /calls/{id}/check | Dry-run a move. Returns the same conflicts, saves nothing |
| POST | /calls/{id}/events | Report a timeline event (first line, ops start, last line) |
| GET | /calls/{id}/documents | The document set with states and deadlines |
| GET | /terminals/{code}/tide | Predicted and observed water level, 10-minute grain |
| GET | /terminals/{code}/cranes | Pool, assignments and maintenance windows |
| DELETE | /calls/{id} | Cancel a call. Only from planned or confirmed |
Move a call
The interesting response is the refusal. A 409 lists every rule the move broke, in the same words the planner sees on the board, plus the next slot that would pass.
// request body { "berth_id": "noordkade-2", "eta": "2026-09-02T20:00:00+02:00" }
// press Send
Create a call
POST /v3/terminals/NLRTM-NOORDKADE/calls
{
"vessel": { "imo": "9704960" },
"berth_id": "noordkade-1",
"eta": "2026-09-04T04:00:00+02:00",
"etd": "2026-09-05T06:00:00+02:00",
"cranes": 4,
"agent": "van-ommeren",
"reference": "MSC-ROT-2026-3311"
}
05Webhooks
Signed with HMAC-SHA256 over the raw body, in Quayside-Signature, with a timestamp you must check is within five minutes. At-least-once delivery, ordered per call, retried eight times over six hours with exponential backoff, then the endpoint is marked failing and your operations contact is paged.
| Event | Fires when | Payload |
|---|---|---|
| call.created | A call is added by anyone, including by EDI | call |
| call.plan.changed | Berth, ETA, ETD or cranes change | call, previous, actor |
| call.conflict.raised | A saved plan starts breaking a rule | call, conflicts[] |
| call.conflict.cleared | The last conflict on a call goes away | call |
| call.status.changed | planned → confirmed → alongside → departed | call, from, to |
| call.event.reported | A timeline event is reported by any source | call, event |
| call.documents.rejected | A document in the set is rejected | call, document, reason |
| tide.window.closing | A deep call’s window closes within 3 h | call, window |
// call.conflict.raised { "id": "evt_01J9F3K2Q8", "type": "call.conflict.raised", "created_at": "2026-09-02T15:04:11+02:00", "data": { "call_id": "nlrtm-2026-0902-msc-eloise", "conflicts": [ { "rule": "overlap", "berth_id": "noordkade-2", "with": "nlrtm-2026-0902-one-orpheus", "hours": 13.0 }, { "rule": "tide_window", "at": "2026-09-02T20:00:00+02:00", "required_m": 0.6, "predicted_m": 0.4 } ] } }
06Errors and limits
Errors are JSON with a stable code, a human message and, where we can, the fix. We do not return 200 with an error inside it.
| Status | Code | Means |
|---|---|---|
| 400 | invalid_request | Malformed body or an unknown field. The field is named. |
| 401 | unauthenticated | Missing, expired or rotated-out token. |
| 403 | insufficient_scope | A read token tried to move a call. |
| 404 | not_found | No such call, or not on a terminal this token can see. |
| 409 | plan_conflict | The move breaks one or more rules. conflicts[] says which. |
| 409 | stale_version | Someone moved the call first. Re-read and retry. |
| 422 | unknown_vessel | The IMO is not in Lloyd’s register or ours. Post particulars. |
| 429 | rate_limited | Over 600 req/min. Retry-After in seconds. |
| 503 | maintenance | Announced on the status page 72 h ahead. |
Every write takes an Idempotency-Key. Retries with the same key inside 24 hours return the first response, which is how you should handle a timeout: retry, do not re-post.
07Changelog
Releases go out every second Tuesday at 06:00 CET, outside every customer’s peak shift change. Breaking changes get six months and a deprecation header.
- 26 Aug 2026v3.11.2Rail segments on crane warnings
Cranes that cannot reach a berth because the rail ends are no longer counted as available to it. Terminal Noordkade and Santos Tecon 3 asked for this within a week of each other.
- 12 Aug 2026v3.11.0Tide windows per movement
Arrival and departure are now checked separately against the channel restriction, instead of the call as a whole.
conflicts[].rulegainstide_window_departure. - 29 Jul 2026v3.10.4Faster board for 40-berth ports
The plan check moved off the request path for terminals over 20 berths. P95 on
PATCH /callsfell from 840 ms to 120 ms at Gdańsk DCT. - 15 Jul 2026v3.10.0Documents API
The document set is now readable and writable over the API, including deadlines and rejection reasons. Previously interface-only.
- 01 Jul 2026v3.9.1BAPLIE 3.1.1
Bay plans in SMDG 3.1.1 are parsed; 2.2 remains supported and will until at least 2028.
- 17 Jun 2026v3.9.0Auto-resolve over the API
POST /terminals/{code}/plan/resolvereturns the moves Quayside would make, without making them. The interface button uses the same endpoint. - 03 Jun 2026v3.8.2Deprecation:
vessel.draft_mRenamed to
draught_mfor consistency with the rest of the API. The old field is returned until 3 December 2026 with a deprecation header. - 20 May 2026v3.8.0Webhook replay
Thirty days of events can be replayed by call reference or by time range, from the interface or
POST /webhooks/{id}/replay.