Cargoplot API
API docs
OpenAPI spec

Creating and updating inquiries

An inquiry is a request for a price. Four endpoints let an integration raise one and look after it:

OperationEndpointAnswers
CreatePOST /v1/inquiry201 with the inquiry
UpdatePATCH /v1/inquiry/{inquiry}200 with the inquiry
CancelPOST /v1/inquiry/{inquiry}/cancel200 with the inquiry
DeleteDELETE /v1/inquiry/{inquiry}204, no body

{inquiry} takes the Cargoplot reference or the numeric id, as the reads do. Every answer that carries an inquiry renders it exactly as GET /v1/inquiry/{inquiry} does, so one parser handles both.

A request body uses the names and types of the inquiry you read. What only Cargoplot sets is left out: id, reference, status, create_time, update_time, requester_user_id, organization_id, total_volume and total_weight. Sending one of them is refused, as is any other key the request does not have, so a typo such as carrier fails loudly instead of being ignored.

Creating an inquiry

POST
/v1/inquiry
curl -X POST 'https://api.cargoplot.com/v1/inquiry' \
  -H 'X-Cargoplot-Key: <token>' \
  -H 'Content-Type: application/json' \
  -d @inquiry.json
inquiry.json
{
  "description": "20 pallets of coffee",
  "origin": {
    "region_code": "NL",
    "locality": "Rotterdam",
    "address_lines": ["Wilhelminakade 123"]
  },
  "destination": { "region_code": "DE", "locality": "Hamburg" },
  "cargo_ready_date": "2026-11-02",
  "cargo_type": "CARGO_TYPE_PALLETS",
  "transport_modes": ["TRANSPORT_MODE_ROAD"],
  "incoterms": ["INCOTERM_FCA"],
  "lcl": {
    "components": [
      { "width": 120, "height": 150, "depth": 80, "weight": 250, "quantity": 4 }
    ]
  }
}

The answer is the new inquiry, shortened here:

json
{
  "id": "1652",
  "status": "STATUS_RECEIVED",
  "reference": "C69M-DFR7",
  "description": "20 pallets of coffee",
  "cargo_ready_date": "2026-11-02",
  "cargo_type": "CARGO_TYPE_PALLETS",
  "hs_code_count": 1,
  "total_volume": 5.76,
  "total_weight": 1000,
  "lcl": {
    "components": [
      { "width": 120, "height": 150, "depth": 80, "weight": 250, "quantity": 4 }
    ],
    "tail_lift": false,
    "pallet_exchange": false
  }
}

A new inquiry starts in STATUS_RECEIVED. Creating it gathers no prices, and the answer carries none.

What a create needs

  • description.
  • origin and destination, each with a region_code (ISO 3166-1 alpha-2, such as NL) and a locality, the city.
  • cargo_ready_date, the date the cargo can be collected, as YYYY-MM-DD. It has to be a date that exists: 2026-02-31 is refused.
  • cargo_type, which also decides the shape of the cargo.
  • At least one transport_modes entry and at least one incoterms entry, each given once. Incoterms are a list because each one is priced on its own.
  • The cargo, in the branch that matches cargo_type:
cargo_typeBranchWhat it needs
CARGO_TYPE_CONTAINERSfclAt least one entry in containers, each with a type and a quantity above zero
CARGO_TYPE_PALLETS, CARGO_TYPE_PACKAGESlclAt least one entry in components, each with a width, height and depth in centimetres, a weight in kilograms and a quantity, all above zero

Send only the branch that matches. For an inquiry created here, total_volume (cubic metres) and total_weight (kilograms) are computed from the components; a container inquiry has neither.

Everything else is optional, and leaving it out gives what the app's inquiry form gives when it is left untouched: false for an option, an empty list for a filter, 0 for a limit. max_transit_days of 0 means no limit, and hs_code_count of 0 means one.

A few options only mean something for one kind of cargo, and setting one that does not fit is refused rather than ignored:

  • storage and timeslot_delivery are for pallets and packages.
  • side_loader_delivery is for containers.
  • tail_lift and pallet_exchange sit inside lcl; min_free_demurrage_days and min_free_detention_days inside fcl.

The ports are UN/LOCODEs (NLRTM), carriers are SCAC codes, and insured_goods_value is an amount in the currency's minor unit: { "currency": "EUR", "amount_cents": 150000 } is €1,500.00.

The vocabularies — cargo types, transport modes, container types, special contents, Incoterms — are the ones in the glossary, sent by name. One exception: TRANSPORT_MODE_BARGE cannot be requested on an inquiry yet, and is refused.

A retried create

Nothing extra is needed to make a create safe to retry. If an identical request from your organization arrives within ten minutes of the first, it creates nothing: the answer is the same 201 with the inquiry the first request made, and the response carries Idempotent-Replayed: true so you can tell.

Identical means the same content, not the same bytes. Key order, whitespace, and sending a default explicitly rather than leaving it out do not make two requests different.

  • After ten minutes, the same body creates a new inquiry. That is how you copy one.
  • An inquiry you have since cancelled or deleted is never replayed; the same body creates a new one.
  • Two identical inquiries within ten minutes on purpose are one inquiry answered twice. Change something that tells them apart, such as the description.

A refused request stores nothing, so once you have fixed it, sending it again is a fresh create.

Updating an inquiry

PATCH changes the fields the body carries and leaves the rest alone. Which fields those are is read from the keys you send, so there is nothing else to supply:

PATCH
/v1/inquiry/C69M-DFR7
curl -X PATCH 'https://api.cargoplot.com/v1/inquiry/C69M-DFR7' \
  -H 'X-Cargoplot-Key: <token>' \
  -H 'Content-Type: application/json' \
  -d '{ "cargo_ready_date": "2026-11-09", "carriers": ["MAEU", "CMDU"] }'

The answer is the whole inquiry as it now stands.

  • A list, an address and insured_goods_value are each one value, replaced by what you send, so send them complete. An origin sent without a postal_code leaves the inquiry without one; a components list replaces every component.
  • fcl and lcl only group their fields, so each key inside them changes on its own. { "fcl": { "min_free_demurrage_days": 7 } } changes that number and leaves the containers as they are.
  • An empty value clears a field: "port_of_loading": "", "carriers": []. A field a create requires cannot be cleared, and answers 400.
  • No update can leave an inquiry without cargo. An empty containers or components list, or a bare "lcl": {}, is refused.
  • Switching cargo_type between containers and pallets or packages needs the new kind of cargo in the same request, since an inquiry for containers cannot keep pallets as its cargo. The options of the kind it leaves are cleared with it: tail_lift and pallet_exchange on a switch to containers, the free-day counts on a switch away. Pallets to packages keeps the components.
  • The rules are judged against the inquiry you would end up with, not only the body. { "storage": true } on a container inquiry is refused, although the body says nothing about containers.

Repeating an update is harmless: the second one leaves the inquiry as the first did.

Cancelling and deleting

POST /v1/inquiry/{inquiry}/cancel withdraws an inquiry: it answers 200 with the inquiry in STATUS_CANCELLED, and no quote will follow. A cancelled inquiry stays in your list and can still be read. Reopening one is done in the app, not over the API. Cancelling an inquiry that is already cancelled changes nothing and answers 200 again.

DELETE /v1/inquiry/{inquiry} answers 204 with no body and takes the inquiry out of your inquiries: it is no longer listed and no longer readable. It is not erased from Cargoplot's records. Deleting it again answers 404, since there is nothing left for you to see — so a retried delete that gets a 404 has succeeded.

Once a quote is accepted

An accepted quote fixes the inquiry, because a shipment is being built from it. From then on, every write answers 409 — update, cancel and delete alike — with one exception: a PATCH that changes only the description.

When a write is refused

StatusWhy
400A field breaks a rule, or the body has a key the request does not have
404No inquiry with that reference or id in your organization
409A quote has been accepted, see above

A rule broken by a field answers with a google.rpc.BadRequest naming the field and why, one entry per problem, and every rule the body breaks is in the same answer. An unknown key is named in message instead. The errors page shows both shapes in full and says which failures are worth retrying.