Documentation

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.

Base URLhttps://api.quayside.nl/v3
Versionv3.11.2 · 26 Aug 2026
AuthBearer token, 90-day rotation
Rate limit600 req/min per terminal

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.

  1. 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.
  2. Confirm your berth modelGET /terminals/{code}/berths returns the berths, their LOA limits, depths alongside and crane counts as Quayside has them. If that is wrong, nothing after it is right.
  3. 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.
  4. Move it and read the conflictsPATCH /calls/{id} with a new ETA or berth. A 409 with a conflicts array means the move was refused; a 200 means the board changed.
  5. Subscribe to changesRegister a webhook endpoint for call.plan.changed so your TOS hears about moves made by planners in the interface, not only by you.
  6. 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.

ObjectKeyBelongs toNotable fields
terminalUN/LOCODE + slug—timezone, tide_station, crane_pool, msw_profile
berthberth_idterminalloa_max_m, depth_m, cranes, rail_segment, bollards
vesselIMO—name, loa_m, beam_m, draught_m, teu, line, flag
callcall_idterminal, berth, vesseleta, etd, ata, atd, cranes, status, conflicts[]
eventevent_idcalltype, reported_at, source, actor, payload
documentdocument_idcallkind, 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.

MethodEndpointDoes
GET/terminals/{code}/berthsThe berth model: limits, depths, cranes, rail segments
GET/terminals/{code}/callsCalls in a window, filtered by status, berth or line
POST/terminals/{code}/callsCreate 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}/checkDry-run a move. Returns the same conflicts, saves nothing
POST/calls/{id}/eventsReport a timeline event (first line, ops start, last line)
GET/calls/{id}/documentsThe document set with states and deadlines
GET/terminals/{code}/tidePredicted and observed water level, 10-minute grain
GET/terminals/{code}/cranesPool, 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.

PATCH /calls/nlrtm-2026-0902-msc-eloise Sandbox
// 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.

EventFires whenPayload
call.createdA call is added by anyone, including by EDIcall
call.plan.changedBerth, ETA, ETD or cranes changecall, previous, actor
call.conflict.raisedA saved plan starts breaking a rulecall, conflicts[]
call.conflict.clearedThe last conflict on a call goes awaycall
call.status.changedplanned → confirmed → alongside → departedcall, from, to
call.event.reportedA timeline event is reported by any sourcecall, event
call.documents.rejectedA document in the set is rejectedcall, document, reason
tide.window.closingA deep call’s window closes within 3 hcall, 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.

StatusCodeMeans
400invalid_requestMalformed body or an unknown field. The field is named.
401unauthenticatedMissing, expired or rotated-out token.
403insufficient_scopeA read token tried to move a call.
404not_foundNo such call, or not on a terminal this token can see.
409plan_conflictThe move breaks one or more rules. conflicts[] says which.
409stale_versionSomeone moved the call first. Re-read and retry.
422unknown_vesselThe IMO is not in Lloyd’s register or ours. Post particulars.
429rate_limitedOver 600 req/min. Retry-After in seconds.
503maintenanceAnnounced 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[].rule gains tide_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 /calls fell 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/resolve returns the moves Quayside would make, without making them. The interface button uses the same endpoint.

  • 03 Jun 2026v3.8.2Deprecation: vessel.draft_m

    Renamed to draught_m for 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.