The first open-source PHP library for SUUS (Röhlig Logistics) freight API integration: shipment creation, pre-flight validation, status tracking, label and document downloads, and business-day calendars for nine countries, with full type safety.
SUUS (now part of Röhlig Logistics) is a freight forwarder and courier operating across Central and Eastern Europe, widely used for B2B parcel and pallet shipments in Poland, Germany, Austria, Switzerland and the neighbouring markets. Any platform that dispatches goods through SUUS has to talk to their API.
The difficulty is that SUUS exposes a legacy RPC/encoded SOAP 1.1 interface with sparse documentation, non-obvious status codes and no official PHP SDK - PHP's own SoapClient cannot even speak to it. suus-php is the first open-source PHP client wrapping that API in a clean, modern interface: typed DTOs for every request and response, a normalised shipment status enum, business-day calendars for nine countries, pre-flight validation that catches rejections before they leave your server, and PHPStan level 8 type safety throughout.
Available on Packagist under the Apache 2.0 licence, it installs with a single Composer command. A working shipment creation fits in under ten lines of code.
Every operation of the SUUS WebApi (WS PK 1.0), each one keyed either by the SUUS waybill number or by your own order reference - because an integration usually holds its own reference, not theirs.
addOrder) and returns the waybill number, your reference and a ready-made tracking URL.ValidationError objects - code, field and message - so your own UI can show the problem before anything is sent.getEvents) plus a normalised status, with every timestamp parsed into a real DateTimeImmutable.Orders are built from typed PHP objects: sender, receiver, packages, dimensions, incoterms, cost group, declared freight. Everything is validated locally before it reaches SUUS, so mistakes surface in your own stack trace rather than as an opaque error code.
Live events are translated into a normalised ShipmentStatus enum - Created, InTransit, Delivered, Cancelled, Failed - instead of cryptic native codes such as ROZF, WTRF or ZWRON. The raw code stays available when you need it.
Shipping labels come back as raw PDF bytes in A4 or thermal A6, alongside shipping orders and loading lists. For a multi-package shipment you can request the label for one specific package rather than the whole set.
Nine typed service objects covering cash on delivery, insurance, e-mail and SMS pre-advice, tail lift, pallet truck, inside delivery and returnable documents - each with its route and product restrictions enforced locally.
SUUS requires two business days of advance notice, and "business day" depends on where the goods are collected. The library ships calendars for all nine countries SUUS operates in and picks the right one from the sender address automatically.
One named constructor switches the client to the SUUS sandbox. No live shipments are created and no production credentials are needed for the first weeks of integration work.
Every method, DTO and enum is fully typed. Your IDE knows exactly what it is working with - no magic arrays, no guessing which fields come back in a response.
A single flag attaches the exact XML SUUS returned to every exception and logs a full report - message, raw response and stack trace - through your PSR-3 logger. That is what turns a bare BTN0001 into something you can act on.
Thirteen SUUS packaging symbols are exposed as an enum - EUR, disposable, industrial, DHP and CHEP pallets, cartons, crates, rolls, DPPL containers, appliances, bundles, hoboks and a catch-all re-handling type. Each package carries a weight and optional dimensions, and returnable/stackable handling is modelled with the rule SUUS enforces (a returnable EUR pallet must be flagged stackable).
The carrier limits are checked before the call, not after the rejection:
Extra services are passed as typed objects and serialised to the SUUS service symbols automatically. Availability differs by route and by order type, and the library rejects an unavailable combination locally instead of letting SUUS answer with a code you then have to look up.
A shipment is international as soon as either party sits outside Poland - only a Poland-to-Poland route counts as domestic, and a German sender delivering to a German receiver is still an international SUUS product. That single fact drives a set of rules that are easy to discover the hard way, so the library enforces all of them before the API call:
Every one of these rules can be relaxed. A ValidationPolicy turns the international-only enforcement off per integrator when a contract allows it, and a RouteClassifier lets you redefine which routes the library treats as international at all - useful when a local in-country contract changes the picture.
Most of the pain in a SUUS integration is not writing the request, it is finding out why one was refused. The library moves that discovery to your side of the wire: validate() runs the exact checks createShipment() performs, without touching the network, and hands back structured errors that reuse the real SUUS codes where one exists.
foreach ($client->validate($order) as $error) {
echo "[{$error->code}] {$error->field}: {$error}\n";
// [PRJ00372] packages[0].returnable: returnable packaging is not available on international routes
}
The same typed errors are carried on the exception thrown by createShipment(), so a form and a background job can share one error renderer.
composer require very-code-com/suus-php
$client = SuusClient::sandbox('ws_yourlogin', 'your_password');
$result = $client->createShipment(new ShipmentOrder(
reference: 'ORDER-2026-001',
sender: new Address('Sender GmbH', 'Musterstr.', '1', '10115', 'Berlin', 'DE', phone: '+4930123'),
receiver: new Address('Odbiorca Sp. z o.o.', 'Marszalkowska', '100', '00-026', 'Warszawa', 'PL', phone: '+48600000'),
packages: [new Package(PackageSymbol::EUR, weightKg: 120.0)],
incoterms: Incoterm::DAP,
orderType: OrderType::B2B,
));
echo $result->shipmentNo; // OPLKRI2600895
echo $result->trackingUrl; // https://portal.suus.com/order-details/OPLKRI2600895
file_put_contents('label.pdf', $client->fetchLabel($result->shipmentNo));
Credentials can equally come from environment variables or a framework config array, and the client accepts a custom transport, a PSR-3 logger and a calendar override for testing and dependency injection.
Six exception types separate the cases you want to handle differently: local validation, rejected credentials, a duplicate reference, a business error from SUUS, a transport failure and an unparseable response. Every one of them exposes the raw response and a full debug report.
Continuous integration runs the unit suite on PHP 8.2, 8.3 and 8.4 with PHPStan level 8 on every push, and a separate nightly workflow runs the whole integration suite against the live SUUS sandbox - creating real sandbox orders and reading their events, colli numbers and documents back. That second suite is the reason the library documents SUUS quirks other clients trip over: the lenghtCm typo in the schema, the namespace swap in responses, and the fact that PRJ000001 means "I could not read your request" at least as often as it means "no such order".
PHP developers connecting a platform - an online shop, an ERP, a warehouse system or a custom logistics workflow - to the SUUS carrier network. For a team shipping B2B freight through SUUS with a PHP backend, the library removes weeks of reverse-engineering a SOAP interface from the integration, and replaces it with a Composer package that a code review can actually read.