Cargoplot API
API docs
OpenAPI spec

Pagination and filtering

Every list endpoint pages the same way, so a client that can page one can page all of them.

Cursors, not page numbers

A list response carries a cursor:

json
{
  "shipments": [
    { "reference": "C3AT-XHM0" }
  ],
  "cursor": {
    "after": "eyJpZCI6NjUxfQ"
  }
}

Pass after back to get the next page:

bash
curl -H 'X-Cargoplot-Key: <token>' \
  'https://api.cargoplot.com/v1/shipment?after=eyJpZCI6NjUxfQ'

cursor.after is absent when there is no next page. That absence is how you know you have reached the end — not an empty array, which you will not get. cursor.before works the same way going backwards, and is absent on the first page.

Treat a cursor as opaque. It is a token that means "resume from here", and its contents are not a contract — decoding one and constructing your own will break.

Why cursors rather than ?page=3: an offset re-counts rows on every request, so a new shipment arriving mid-pass shifts everything down and you silently skip a record. A cursor resumes from a fixed point instead.

Page size

limit accepts 50 by default and 100 at most.

Asking for more than the maximum is refused with a 400, not quietly reduced. That is deliberate: a silently truncated page looks exactly like the end of the data, and a client that trusts it skips records without ever knowing.

Sorting

order_by chooses the field, order the direction (ORDER_ASC or ORDER_DESC, newest first by default). The available order_by values differ per resource — see the spec for each endpoint's list.

The sort is part of the cursor. Once you start paging, changing order or order_by invalidates the cursor you were given. Pick the sort for the whole pass, and start again from no cursor if you need to change it.

Filters narrow by field. They are repeatable where a resource can be in one of several states:

bash
'?filter.status=STATUS_ACTIVE&filter.status=STATUS_ON_HOLD'

Date filters

Every filter on a date accepts four shapes, so a caller can express an open or a closed window without a second parameter:

ValueMatches
2026-07-01that whole day
gte:2026-07-01that day and everything after it
lte:2026-07-31that day and everything before it
2026-07-01..2026-07-31both days and everything between them
bash
'?filter.etd=gte:2026-07-01&filter.eta=2026-07-01..2026-07-31'

Every bound is a whole day, and both ends are inclusive. lte:2026-07-31 keeps a shipment arriving at 23:50 on the 31st, and a closed range keeps both of its end days. Days are UTC, so a bound is not nudged by the caller's own offset. Sub-day precision is not accepted; to narrow within a day, filter on the day and compare timestamps client-side.

A record whose field is empty is not matched. A shipment with no ETA is outside filter.eta=gte:2026-07-01 and outside lte:2026-07-01, so the two do not add up to the full list.

A value that is not one of the four shapes is refused with a 400 naming the forms that are accepted, rather than dropped. That is deliberate: an ignored filter answers a broader question than the one asked, and a page of everything looks much like a page of matches.

A backwards closed range is a shape the four forms allow, so it is not refused. 2026-07-31..2026-07-01 asks for records on or after the 31st and on or before the 1st, which nothing satisfies, and the result is an empty page rather than an error.

Which fields are dates differs by resource, and the spec is the list: a shipment filters on etd and eta, an invoice on issue_date and due_date, an inquiry on cargo_ready_date.

search is free text across the fields a human would search, which differ by resource: a shipment's reference, description and origin or destination city; an invoice's reference. It narrows the result set; it does not reorder it, so a better match does not float to the top.

Filters and search combine, and combine with paging. Applying them server-side is almost always cheaper than fetching everything and filtering locally — and it spends far less of your rate limit.

Walking a whole list

bash
cursor=''
while :; do
  page=$(curl -sH "X-Cargoplot-Key: $TOKEN" \
    "https://api.cargoplot.com/v1/shipment?limit=100&after=$cursor")
  echo "$page" | jq -r '.shipments[].reference'
  cursor=$(echo "$page" | jq -r '.cursor.after // empty')
  [ -n "$cursor" ] || break
done

The loop ends because cursor.after disappears, which is the only reliable signal. Stopping when a page comes back shorter than limit is a common habit and it is wrong here — a filtered page can be short and still have more after it.