Creating and updating inquiries
An inquiry is a request for a price. Four endpoints let an integration raise one and look after it:
| Operation | Endpoint | Answers |
|---|---|---|
| Create | POST /v1/inquiry | 201 with the inquiry |
| Update | PATCH /v1/inquiry/{inquiry} | 200 with the inquiry |
| Cancel | POST /v1/inquiry/{inquiry}/cancel | 200 with the inquiry |
| Delete | DELETE /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
curl -X POST 'https://api.cargoplot.com/v1/inquiry' \
-H 'X-Cargoplot-Key: <token>' \
-H 'Content-Type: application/json' \
-d @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:
{
"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.originanddestination, each with aregion_code(ISO 3166-1 alpha-2, such asNL) and alocality, the city.cargo_ready_date, the date the cargo can be collected, asYYYY-MM-DD. It has to be a date that exists:2026-02-31is refused.cargo_type, which also decides the shape of the cargo.- At least one
transport_modesentry and at least oneincotermsentry, 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_type | Branch | What it needs |
|---|---|---|
CARGO_TYPE_CONTAINERS | fcl | At least one entry in containers, each with a type and a quantity above zero |
CARGO_TYPE_PALLETS, CARGO_TYPE_PACKAGES | lcl | At 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:
storageandtimeslot_deliveryare for pallets and packages.side_loader_deliveryis for containers.tail_liftandpallet_exchangesit insidelcl;min_free_demurrage_daysandmin_free_detention_daysinsidefcl.
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:
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_valueare each one value, replaced by what you send, so send them complete. Anoriginsent without apostal_codeleaves the inquiry without one; acomponentslist replaces every component. fclandlclonly 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 answers400. - No update can leave an inquiry without cargo. An empty
containersorcomponentslist, or a bare"lcl": {}, is refused. - Switching
cargo_typebetween 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_liftandpallet_exchangeon 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
| Status | Why |
|---|---|
400 | A field breaks a rule, or the body has a key the request does not have |
404 | No inquiry with that reference or id in your organization |
409 | A 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.