Skip to main content

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.

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.

ResourceMethodCovers
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.

ExceptionHTTP statusWhen
AuthenticationException401Invalid or missing API key
NotFoundException404Resource does not exist
ValidationException422Invalid request payload
EcourierException5xx / otherUnexpected 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.