API documentation

Integrate the SAMO-TRANS exchange into your TMS or CRM

API documentation

Changelog

History of changes to the SAMO-TRANS public API.

Newest entries on top. Every change to the public contract lands here.

For the compatibility rules, see Versioning.

v1.1

  • Added: transport listings now have an optional vehicleType field, independent from carTypes. The new GET /reference/vehicle-types endpoint supplies valid codes; null in a PATCH clears the value. Responses return either the code or null. The field is rejected for cargo listings. This is additive: existing records and integrations remain valid.

  • Added: GET /reference/car-types now also returns the specialised body codes box_type, walking_floor_type, coil_carrier_type, timber_truck_type, concrete_mixer_type, and hooklift_type. This is an additive dictionary change; integrations should read the endpoint and tolerate new codes instead of maintaining a closed local enum.

  • Added: listings now carry an optional temperature range. Create and update bodies accept temperatureMin and temperatureMax (°C, range −99…+99, either bound may be omitted); responses return temperatureMinC and temperatureMaxC, both null when no range is set. The fields are accepted only together with a temperature-controlled body (ref, ref-tushe, izoterm), otherwise the request fails with 400 validation_error. A PATCH that changes the body type to a non-temperature-controlled one resets both bounds. The change is additive: existing integrations lose nothing and may simply ignore the new fields.

  • Changed: contacts are now stripped from description before a listing is stored — phone numbers, e-mail addresses, @handles and links (including bare domains and messenger links). Applies to POST /proposals/cargo, POST /proposals/transport, POST /proposals/batch and PATCH /proposals/{id}. This is not validation: a request carrying a phone number in the description still succeeds — there is no error, the listing is simply saved with the cleaned text. Consequence for integrations: the description in the response and on any later GET may be shorter than the one you sent — do not compare those strings for equality. Text without contacts comes back untouched: trip dates, tax numbers, weights and prices are never mistaken for a phone number. The field's description in the reference was updated.

  • Fixed: a non-numeric locality id no longer answers 500 internal. startLocalities / endLocalities on GET /proposals/search, plus startLocations[].osmId and endLocations[].osmId when creating or updating a listing, accept only a positive integer OSM ID. Anything else — letters, a decimal, a sign, a value past the bigint range — now returns 400 with code validation_error; such a request used to fail with 500 internal. Valid ids behave exactly as before.

  • Changed: POST /proposals/{id}/bump is now also available to an owner or manager of the company the listing belongs to — exactly the rights that already applied to update, archive and delete. Previously only the listing's author or contact person could bump it, so a company owner got 403 forbidden on their own employee's listing while being able to edit it or take it off air. Nothing is narrowed — access is only widened, so existing integrations lose nothing.

  • GET /proposals/search accepts startLocalityCountries and endLocalityCountries — the country of each entry in startLocalities / endLocalities, in the same order. With them the results also include listings posted for that whole country ("any direction"), not only the ones tied to the city itself. Both are optional — without them search behaves as before.

  • GET /employees — list company staff.

  • GET /employees/{id}/proposals-count — how many listings name the employee as contact.

  • PATCH /employees/{id}/status — activate or temporarily deactivate an employee.

  • PATCH /employees/{id} — change role or display name.

  • New scopes employees:read and employees:write. Previously issued keys do not carry them — create a new key.

  • contactPersonId is now accepted when creating and updating a listing (batch included). Only the company owner and employees with status ACTIVE are allowed.

  • Changed: GET /localities/search now also returns regions (type: "region") — the response used to carry countries and localities only, so a region could not be found by name. Order is countries → regions → localities. No new fields, one more kind of array entry — if you expect an osmId on every record, filter by type === "locality".

  • Changed: GET /proposals/search returns closed listings after every open one, newest first within each group. Ordering used to be by createdAt alone, so with includeInactive=true closed listings were mixed in among the open ones. The response is unchanged — only its order; if you relied on a purely chronological sequence, sort by createdAt on your side.

  • Changed: description on listing create and update (batch included) is capped at 250 characters. Its length used to go unchecked — any string was accepted. A longer text now gets a 400 with code validation_error; trim the description on your side. The cap is also visible in the reference as the field's maxLength.

  • Fixed: GET /employees now always returns the company owner, with role OWNER. Companies carried over from the previous platform had no such record, so their staff list started with hired people. No field changed — the array simply has one more element. If you size your staff by the length of the response, count the owner in.

  • Fixed: avatar (on employees and on a listing's contact person) is always an absolute URL. Some records used to carry a bare storage key (avatars/….jpeg), which a client resolved against its own address and got a 404 for. The field's type is unchanged — it is the same URL string.

  • Fixed: includeInactive on GET /proposals/search used to read any non-empty value as true, so even includeInactive=false returned closed listings. It is now read as written: true/1/yes/on enable it, false/0/no/off disable it, and an unrecognised value behaves as if the parameter were absent. If your integration sent false and relied on getting closed listings back, it will now receive a different set — send true explicitly.

v1.0

The first public release of the API.

Dictionaries

  • GET /reference/countries, /reference/regions — geography.
  • GET /reference/car-types, /load-types, /permits, /currencies, /payment-types — code dictionaries with labels in five languages.
  • GET /localities/search — locality search, the source of route osmIds.

Exchange

  • GET /proposals/search — search the whole exchange with the web app's filters, plus updatedSince for incremental sync.
  • GET /proposals/{id} — listing detail.
  • GET /companies/{id}, GET /companies/{id}/reviews — counterparty profile and approved reviews.

Your own listings

  • POST /proposals/cargo, POST /proposals/transport — creation.
  • POST /proposals/batch — up to 25 listings per request with a per-item result.
  • GET /proposals/my — listings of the company the key belongs to.
  • PATCH /proposals/{id}, POST /proposals/{id}/bump, /archive, /restore, DELETE /proposals/{id}.

Platform

  • Authentication with samo_live_ keys carrying reference:read, proposals:read and proposals:write scopes plus an optional IP allowlist.
  • Per-key rate limits with X-RateLimit-* headers and the stable rate_limited / quota_exceeded codes.
  • Idempotency-Key support for safe write retries.
  • Webhooks for proposal.created / updated / deleted and proposal.matched, signed with X-Samo-Signature.
  • The stable error envelope { error: { code, message, requestId } }.