PHP SDK
A clean, modern PHP SDK for the eCourier API, built on Saloon. It wraps the full REST API (v1) and gives you typed requests, typed responses, automatic pagination, and clear exceptions for every error case.
- Package:
ecourier/ecourier - Repository: github.com/utecca/ecourier-php-sdk
- Requirements: PHP 8.3+
Using Laravel? See the Laravel package, which wraps this SDK with a service provider, config, and webhook events.
Installation
composer require ecourier/ecourier
Getting started
Instantiate the connector with an API key. The key prefix determines the mode: pk_test_ for test, pk_live_ for production.
use Ecourier\EcourierConnector;
// Test mode
$ecourier = new EcourierConnector(apiKey: 'pk_test_your_key_here');
// Production
$ecourier = new EcourierConnector(apiKey: 'pk_live_your_key_here');
All requests are authenticated automatically via Authorization: Bearer — you never touch headers yourself.
Resources
The SDK is organized into four resources, accessible as methods on the connector.
| Resource | Method | Covers |
|---|---|---|
| Companies | $ecourier->companies() | List, create, update, delete, and inspect companies |
| Documents | $ecourier->documents() | Send, receive, and inspect documents, such as invoices and credit notes |
| Participants | $ecourier->participants() | List, create, update, delete, and inspect participants |
| Lookup | $ecourier->lookup() | Look up network participants by channel, scheme, and ID |
Example: sending a document
use Ecourier\Data\Invoice\InvoiceDocumentData;
use Ecourier\Data\Invoice\InvoiceLineData;
use Ecourier\Data\Invoice\InvoicePartyData;
use Ecourier\Data\Invoice\InvoiceTotalsData;
use Ecourier\Data\Invoice\ParticipantIdentifier;
use Ecourier\Enums\Channel;
use Ecourier\Enums\Currency;
use Ecourier\Enums\DocumentType;
use Ecourier\Enums\IdentifierScheme;
$invoice = new InvoiceDocumentData(
type: DocumentType::Invoice,
id: 'INV-2024-001',
issueDate: '2024-06-01',
currency: Currency::DKK,
supplier: new InvoicePartyData(
participant: new ParticipantIdentifier(IdentifierScheme::DK_CVR, '12345678'),
),
customer: new InvoicePartyData(
participant: new ParticipantIdentifier(IdentifierScheme::DK_CVR, '87654321'),
),
lines: [new InvoiceLineData(id: 1)],
totals: new InvoiceTotalsData(
subtotalAmount: '1000.00',
taxAmount: '250.00',
totalAmount: '1250.00',
),
);
$document = $ecourier->documents()->sendJson(Channel::Peppol, $invoice);
echo $document->id; // 01kmkdaf55vrrecfy70180tpr6
echo $document->e2eMessageUuid; // ddc3b3ef-cbd4-4630-9d65-896b3e1abc61
sendJson() converts the typed payload to the correct XML schema automatically. If you need full control, sendXml() accepts a raw UBL document instead.
Example: listing with pagination
list() returns a lazy paginator, fetching pages from the API on demand as you consume items:
foreach ($ecourier->documents()->list()->items() as $document) {
echo $document->id;
}
In a Laravel app, collect() wraps the paginator in a LazyCollection:
$ecourier->documents()->list(perPage: 50)
->collect()
->filter(fn ($doc) => $doc->status === DocumentStatus::Delivered)
->each(fn ($doc) => ProcessDocument::dispatch($doc));
Exception handling
The SDK throws typed exceptions for every error case — no checking status codes manually. All exceptions extend EcourierException, so you can catch broadly or narrowly.
| Exception | HTTP status | When |
|---|---|---|
AuthenticationException | 401 | Invalid or missing API key |
NotFoundException | 404 | Resource does not exist |
ValidationException | 422 | Invalid request payload |
EcourierException | 5xx / other | Unexpected server errors |
use Ecourier\Exceptions\AuthenticationException;
use Ecourier\Exceptions\EcourierException;
use Ecourier\Exceptions\NotFoundException;
use Ecourier\Exceptions\ValidationException;
try {
$document = $ecourier->documents()->find('doc_missing');
} catch (NotFoundException $e) {
echo $e->getMessage();
} catch (ValidationException $e) {
foreach ($e->getErrors() as $field => $messages) {
echo "{$field}: " . implode(', ', $messages) . PHP_EOL;
}
} catch (AuthenticationException $e) {
// bad API key
} catch (EcourierException $e) {
$e->getResponse()->status();
}
Testing & mocking
The SDK is built on Saloon, which ships with a first-class mock client — no HTTP requests are made in your tests.
use Ecourier\EcourierConnector;
use Ecourier\Enums\DocumentStatus;
use Ecourier\Requests\Documents\GetDocumentRequest;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;
$mockClient = new MockClient([
GetDocumentRequest::class => MockResponse::make(
body: ['id' => 'doc_01xyz', 'status' => 'Delivered'],
status: 200,
),
]);
$ecourier = new EcourierConnector(apiKey: 'pk_test_fake');
$ecourier->withMockClient($mockClient);
$document = $ecourier->documents()->find('doc_01xyz');
expect($document->status)->toBe(DocumentStatus::Delivered);
$mockClient->assertSent(GetDocumentRequest::class);
Learn more
For the full resource and method reference — companies, participants, lookup, all data objects, and enums — see the package README on GitHub.