TOVRReference

Shipments

Create a shipment (a nested tree of stops, contents, costs, and identifiers), list your shipments, get one by its master ID, and change its status or visibility.

The shipments endpoints let you push freight demand into TOVR and read it back. A shipment is a nested tree — see Domain model for the shape and Concepts › Domain model for what it means. All endpoints require a bearer token; your organization is inferred from it.

Create a shipment

POST /api/v1/shipments

Send the shipment tree (identifiers, attributes, contents, costs, stops, route segments, …). The body is validated against the shipment insert schema; omit any id — providing one is treated as an update, which is not supported (see below).

curl -X POST https://tovr.eu/api/v1/shipments \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "identifiers": [{ "type": "INTERNAL_ID", "value": "SHIP-ABC-123" }],
    "routeInfo": {
      "stops": [
        { "id": "s1", "type": "PICKUP",   "sequence": 1, "location": { "city": "Wrocław",   "postal_code": "50-148", "country": "PL" } },
        { "id": "s2", "type": "DELIVERY", "sequence": 2, "location": { "city": "Frankfurt", "postal_code": "60311", "country": "DE" } }
      ],
      "route_segments": [{ "from_stop_id": "s1", "to_stop_id": "s2", "transportation_mode": "TRUCK" }]
    }
  }'

A successful create returns 201 Created with the persisted shipment, including generated IDs and the expanded child collections (shipment_identifiers, stops, route_segments, …).

List shipments

GET /api/v1/shipments (scope shipments:read)

Returns your organization's shipments in pages of limit (1–100, default 25), ordered by created_at unless sort says otherwise. Pass the response's next_cursor as cursor to read the next page. Optional query parameters:

ParameterDescription
sortcreated_at (the default) or pickup_at.
statusComma-separated shipment statuses. An unknown value is rejected, not ignored.
availabilityComma-separated availability values.
platformComma-separated source platforms.

List rows are a summary; GET a shipment by its ID for the full tree.

Get a shipment

GET /api/v1/shipments/{master_shipment_id}

Returns the shipment and its relation tree (stops, contents, costs, identifiers, events, route segments).

Update a shipment

PATCH /api/v1/shipments/{master_shipment_id} (scope shipments:write)

Two fields can be changed, and nothing else — an unknown field is rejected:

  • status — moved through the same status guard the dashboard uses, so only legal transitions succeed, and a status that needs more data is refused until the shipment has it.
  • visibility — who else can see the shipment.

The response is the updated shipment. A POST always creates: sending an id to POST /api/v1/shipments is rejected with 422 and a pointer to PATCH.

Contact requirements

Two rules govern who has to be reachable, and they apply at different moments.

Every shipment must name a contact. contacts needs at least one entry with a name and either an email or a phone. A body without one is refused with 422 and code: "contact_required".

This is a breaking change. A caller that has been creating shipments with an empty contacts array will start receiving 422 contact_required. Add a contact to the body; the code is stable and specific so you can branch on it.

Publishing also requires the organization itself to be reachable. Creating a shipment with visibility of "public" or "platform" requires your organization to have stated both a contact email address and a phone number (Settings → General). Without them the request is refused with 422 and code: "org_not_reachable", and the message names which half is missing.

This second rule deliberately does not apply to visibility: "private". A published load reaches every carrier on the network, and they need a route back to you; a private one reaches nobody, so there is nothing to be reachable for. Gating creation itself would fail an entire sync because a person never opened a settings page — something no integration can fix from inside a request.

Errors

401 for a missing or invalid token; 403 when the token lacks the required scope; 409 when the external_id collides (duplicate_external_id, or external_id_owned_by_another_org when it belongs to another organization); 422 for a validation failure, an id in a create body, and the two contact rules above; 500 on a server error. See API conventions.

On this page