A PHP client library for Bring's developer APIs: Shipping Guide, Booking, Tracking, Reports, Postal Code, the new Address API, Pickup Point, Modify Delivery, and Order Management (REST).
Used in production by a large Norwegian wholesaler.
composer require crakter/bringapi- PHP 8.2 or newer
- A PSR-18 HTTP client (Guzzle 7 is the suggested default)
simplexmlextension (built-in on most distributions)phpoffice/phpspreadsheetonly if you call Reports endpoints that return XLS
| API | Coverage | Bring docs |
|---|---|---|
| Shipping Guide (v2) | price / delivery time / products | link |
| Booking | book, pickup order, customers | link |
| Tracking | track, signature image | link |
| Reports | list, generate, status, download, invoices | link |
| Postal Code (legacy) | single lookup | link |
| Address (new) | postal-code lookup, suggestions, mailbox-delivery dates | link |
| Pickup Point | all / by id / by postal code / by location (NO/SE/DK/FI only) | link |
| Modify Delivery | stop, change address, update contact (NO/SE/DK only) | link |
| Order Management (REST) | get order, packaging list | link |
The SOAP variant of Order Management is intentionally out of scope.
use Bring\Api\ApiClient;
use Bring\Api\Auth\Credentials;
use Bring\Api\Enum\Country;
$bring = ApiClient::withCredentials(new Credentials(
uid: 'me@example.com',
apiKey: getenv('BRING_API_KEY'),
clientUrl: 'https://example.com',
));
// Modern address lookup
$result = $bring->address()->postalCode(Country::NO, '0150');
echo $result->city; // "OSLO"
// Pickup points near a postal code
foreach ($bring->pickupPoint()->byPostalCode(Country::NO, '0150')->pickupPoints as $pp) {
echo "{$pp->name} — {$pp->address}\n";
}
// Tracking
$tracking = $bring->tracking()->track('TESTPACKAGE-AT-PICKUPPOINT');
echo $tracking->latestEvent()?->description;Calls that support X-Bring-Test-Indicator (Booking, Modify Delivery) are
toggled at the facade:
$bring = ApiClient::withCredentials($creds)->withTestMode(true);
// Every request now carries X-Bring-Test-Indicator: trueuse Bring\Api\Dto\{Address, Contact, Dimensions, Package};
use Bring\Api\Endpoint\Booking\BookingRequest;
use Bring\Api\Enum\{Country, Product};
$request = BookingRequest::single(
schemaVersion: '1',
customerNumber: 'PARCELS_NORWAY-10001234567',
product: Product::HOME_DELIVERY_PARCEL,
sender: new Address(
name: 'Acme AS', addressLine: 'Sandakerveien 24c', addressLine2: null,
postalCode: '0473', city: 'Oslo', countryCode: Country::NO,
contact: new Contact(name: 'Pickup Person', phoneNumber: '+4799999999'),
),
recipient: new Address(
name: 'John Doe', addressLine: 'Storgata 1', addressLine2: null,
postalCode: '5003', city: 'Bergen', countryCode: Country::NO,
),
packages: [new Package(
weightInKg: 2,
dimensions: new Dimensions(lengthInCm: 30, widthInCm: 20, heightInCm: 15),
)],
);
$resp = $bring->booking()->book($request);
foreach ($resp->consignments as $c) {
echo "Consignment {$c->confirmation}\n";
}Credentials wraps the API key behind #[\SensitiveParameter] (PHP 8.2+
scrubs it from stack traces) and masks it in print_r / var_dump output —
debug dumps only show a SHA-256 fingerprint.
Pass any PSR-3 logger to ApiClient::withCredentials() and it is automatically
wrapped in a RedactingLogger that strips X-Mybring-* headers and the raw
API key from every log line:
$bring = ApiClient::withCredentials($creds, logger: $monolog);BringApiException never embeds the raw response body in getMessage() —
Bring occasionally echoes credentials in error envelopes. Callers that want
the response body can call BringApiException::getResponse() explicitly.
use Bring\Api\Exception\{BringApiException, BringTransportException, BringException};
try {
$bring->shippingGuide()->price($request);
} catch (BringApiException $e) {
// Bring returned 4xx/5xx — parsed error codes in $e->getErrors()
error_log("Bring rejected request (HTTP {$e->getStatusCode()})");
foreach ($e->getErrors() as $err) {
error_log(" {$err->code}: {$err->message}");
}
} catch (BringTransportException $e) {
// PSR-18 network failure (DNS, TLS, timeout); $e->getPrevious() has the cause
} catch (BringException $e) {
// Catch-all for anything this library throws
}See UPGRADE-4.0.md for the full mapping. v3 classes
(Crakter\BringApi\*) still ship and still work — they are marked
@deprecated and will be removed in 5.0.
Set credentials in your environment first:
export BRING_UID="me@example.com"
export BRING_API_KEY="1234abc-abcd-1234-5678-abcd1234abcd"
export BRING_CUSTOMER_NUMBER="PARCELS_NORWAY-10001123123"Then run any example from the project root:
php examples/v4_PostalCode.php 0150
php examples/v4_PickupPoint.php 0150
php examples/v4_Tracking.php TESTPACKAGE-AT-PICKUPPOINT
php examples/v4_ShippingGuidePrice.php 0150 5003
php examples/v4_BookAndPickup.php # uses test mode, no labels generatedcomposer install
composer qa # full gate: cs + phpstan + psalm + phpunit
composer test # all tests (legacy + v4)
composer test-coverage # coverage in coverage/ + coverage.xml
composer phpstan # PHPStan level 8 on v4
composer psalm # Psalm errorLevel 4 on v4
composer cs # php-cs-fixer dry run
composer docs # build API docs into docs/buildGenerated with phpDocumentor 3 (the abandoned Sami
generator was dropped in 4.0). bin/build-docs downloads the official
phar into tools/phpdoc.phar on first run — we deliberately do NOT
require phpDocumentor through Composer because its transitive dependency
tree conflicts with most application stacks.
composer docs # build into docs/build
composer docs-clean # wipe + rebuild
bin/build-docs --force # any extra phpdoc flags pass throughOpen docs/build/index.html in a browser, or let CI publish it:
.github/workflows/docs.yml builds on every push to master/main and
deploys to GitHub Pages (enable Pages in repo settings → Pages → Source =
GitHub Actions to activate). Every workflow run also uploads the rendered
docs as an artifact named api-docs.
- CHANGELOG.md — release history (Keep a Changelog format)
- UPGRADE-4.0.md — v3 → v4 migration guide
- SECURITY.md — vulnerability reporting
- CONTRIBUTING.md — coding standards and PR process
MIT