PHPackages                             4bdullatif/eudr-php-client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. 4bdullatif/eudr-php-client

ActiveLibrary[API Development](/categories/api)

4bdullatif/eudr-php-client
==========================

Framework-agnostic PHP client for the EU Deforestation Regulation (EUDR) API with WS-Security authentication

60[1 issues](https://github.com/4bdullatif/eudr-php-client/issues)PHPCI passing

Since Feb 5Pushed 3mo agoCompare

[ Source](https://github.com/4bdullatif/eudr-php-client)[ Packagist](https://packagist.org/packages/4bdullatif/eudr-php-client)[ RSS](/packages/4bdullatif-eudr-php-client/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

EUDR PHP Client
===============

[](#eudr-php-client)

[![PHPStan Level 9](https://camo.githubusercontent.com/1bc07920f0d36e55c17e1d38b1caa132cc605f51a82b388c962870b9a747b898/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e2e737667)](https://phpstan.org/)[![PHP 8.3+](https://camo.githubusercontent.com/52cd2dd254fc3ea7b3de30c3634e04f868fb0116d7312e2c356074f11d771ce9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e332532422d3838393242462e737667)](https://www.php.net/)[![License: MIT](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](LICENSE)

A framework-agnostic PHP client for the [EU Deforestation Regulation (EUDR)](https://environment.ec.europa.eu/topics/forests/deforestation/regulation-deforestation-free-products_en) TracesNT SOAP API. Supports both V1 and V2 endpoints with full WS-Security authentication.

Features
--------

[](#features)

- Full V1 and V2 API coverage: Submit, Amend, Retract, Retrieve, and cross-supply-chain operations
- WS-Security password digest authentication
- Immutable request builders with fluent API
- PSR-18 HTTP client with auto-discovery
- Configurable middleware pipeline (retry, logging, custom)
- Strict types and PHPStan level 9 throughout

---

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Supported Operations](#supported-operations)
- [Usage](#usage)
    - [Submitting a DDS](#submitting-a-dds)
    - [Amending a DDS](#amending-a-dds)
    - [Retracting a DDS](#retracting-a-dds)
    - [Retrieving a DDS](#retrieving-a-dds)
    - [Cross-Supply-Chain Retrieval](#cross-supply-chain-retrieval)
    - [Echo Service](#echo-service)
- [Middleware](#middleware)
- [Error Handling](#error-handling)
- [API Versions](#api-versions)
- [Development](#development)
- [Architecture](#architecture)
- [License](#license)

---

Requirements
------------

[](#requirements)

- PHP 8.3 or higher
- `ext-dom`, `ext-libxml`, `ext-mbstring`, and `ext-simplexml`
- A PSR-18 HTTP client (e.g. Guzzle, Symfony HttpClient)
- PSR-17 HTTP factories

Installation
------------

[](#installation)

```
composer require 4bdullatif/eudr-php-client
```

If you don't already have a PSR-18 HTTP client:

```
composer require guzzlehttp/guzzle guzzlehttp/psr7
```

Quick Start
-----------

[](#quick-start)

```
use Eudr\Config\Config;
use Eudr\Config\Credentials;
use Eudr\Data\Commodity;
use Eudr\Data\Producer;
use Eudr\Data\SpeciesInfo;
use Eudr\Enums\ActivityType;
use Eudr\Enums\OperatorType;
use Eudr\EudrClient;
use Eudr\Requests\V2\SubmitDdsRequest;

$client = new EudrClient(
    config: new Config(
        baseUrl: 'https://webgate.acceptance.ec.europa.eu/tracesnt/ws',
        credentials: new Credentials(
            username: 'your-username',
            authKey: 'your-auth-key',
            clientId: 'your-client-id',
        ),
    ),
);

$request = SubmitDdsRequest::make()
    ->withOperatorType(OperatorType::OPERATOR)
    ->withActivityType(ActivityType::IMPORT)
    ->withInternalReference('MY-REF-2024-001')
    ->addCommodity(
        Commodity::make()
            ->position(1)
            ->description('Tropical hardwood lumber')
            ->hsHeading('440399')
            ->netWeight(5000.0)
            ->addSpeciesInfo(new SpeciesInfo('Swietenia macrophylla', 'Mahogany'))
            ->addProducer(new Producer('BR', base64_encode('{"type":"Point","coordinates":[-47.87,-15.79]}')))
            ->build(),
    );

$response = $client->dds()->submit($request);

echo $response->ddsIdentifier; // UUID of the created DDS
```

Configuration
-------------

[](#configuration)

### Direct construction

[](#direct-construction)

```
use Eudr\Config\Config;
use Eudr\Config\Credentials;

$config = new Config(
    baseUrl: 'https://webgate.acceptance.ec.europa.eu/tracesnt/ws',
    credentials: new Credentials(
        username: 'your-username',
        authKey: 'your-auth-key',
        clientId: 'your-client-id',
    ),
    timeout: 30,              // HTTP timeout in seconds (default: 30)
    validateRequests: true,   // Validate before sending (default: true)
    logger: $psrLogger,       // Optional PSR-3 logger
);
```

### From array

[](#from-array)

```
$config = Config::fromArray([
    'baseUrl' => 'https://webgate.acceptance.ec.europa.eu/tracesnt/ws',
    'username' => 'your-username',
    'authKey' => 'your-auth-key',
    'clientId' => 'your-client-id',
    'timeout' => 30,
    'validateRequests' => true,
]);
```

### Providing your own HTTP client

[](#providing-your-own-http-client)

PSR-18/PSR-17 implementations are auto-discovered via [php-http/discovery](https://github.com/php-http/discovery). You can also inject them explicitly:

```
$client = new EudrClient(
    config: $config,
    httpClient: $psrHttpClient,
    requestFactory: $psrRequestFactory,
    streamFactory: $psrStreamFactory,
    middleware: [
        new RetryMiddleware(maxAttempts: 3, baseDelayMs: 200),
    ],
);
```

Supported Operations
--------------------

[](#supported-operations)

OperationDescriptionV1V2SubmitCreate a new DDSYYAmendModify an existing DDSYYRetractCancel/withdraw a DDSYYRetrieveGet DDS info by UUIDYYRetrieveManyBatch retrieve up to 100 UUIDsYYRetrieveByReferenceGet DDS by internal reference numberYYGetStatementByIdentifiersCross-supply-chain retrievalYYGetReferencedDdsFollow referenced DDS chain-YEchoTest connectivity and authenticationYY---

Usage
-----

[](#usage)

### Submitting a DDS

[](#submitting-a-dds)

All request objects are immutable. Each `with*`/`add*` method returns a new instance.

```
use Eudr\Data\Address;
use Eudr\Data\Commodity;
use Eudr\Data\EconomicOperator;
use Eudr\Data\Producer;
use Eudr\Data\SpeciesInfo;
use Eudr\Enums\ActivityType;
use Eudr\Enums\OperatorType;
use Eudr\Requests\V2\SubmitDdsRequest;

$request = SubmitDdsRequest::make()
    ->withOperatorType(OperatorType::OPERATOR)
    ->withActivityType(ActivityType::IMPORT)
    ->withInternalReference('MY-REF-2024-001')
    ->withCountryOfActivity('DE')
    ->withBorderCrossCountry('NL')
    ->withComment('Annual timber import')
    ->withGeoLocationConfidential()
    ->withOperator(new EconomicOperator(
        name: 'Example GmbH',
        address: new Address(
            street: 'Hauptstrasse',
            number: '42',
            postcode: '10115',
            city: 'Berlin',
            countryCode: 'DE',
        ),
        email: 'contact@example.com',
        phone: '+49 30 1234567',
        referenceNumbers: [
            ['identifierType' => 'EORI', 'identifierValue' => 'DE123456789'],
        ],
    ))
    ->addCommodity(
        Commodity::make()
            ->position(1)
            ->description('Tropical hardwood lumber')
            ->hsHeading('440399')
            ->volume(200.0)
            ->netWeight(5000.0)
            ->numberOfUnits(100)
            ->percentageEstimationOrDeviation(2.5)
            ->supplementaryUnit('m3')
            ->supplementaryUnitQualifier('CBM')
            ->addSpeciesInfo(new SpeciesInfo('Swietenia macrophylla', 'Mahogany'))
            ->addProducer(new Producer(
                country: 'BR',
                geometryGeojson: base64_encode('{"type":"Point","coordinates":[-47.87,-15.79]}'),
                name: 'Brazilian Forest Co',
            ))
            ->build(),
    )
    ->addAssociatedStatement('REF-2024-001', 'VER-2024-001');

$response = $client->dds()->submit($request);

$response->ddsIdentifier; // "3f09ab3f-4c97-4663-8463-89d58f1d646b"
$response->isSuccess();   // true
```

### Amending a DDS

[](#amending-a-dds)

Modify an existing DDS in `AVAILABLE` status. The activity type cannot be changed from the original.

```
use Eudr\Requests\V2\AmendDdsRequest;

$request = AmendDdsRequest::make()
    ->withDdsIdentifier('3f09ab3f-4c97-4663-8463-89d58f1d646b')
    ->withOperatorType(OperatorType::OPERATOR)
    ->withActivityType(ActivityType::IMPORT)
    ->withInternalReference('MY-REF-2024-001-AMENDED')
    ->addCommodity($commodity);

$response = $client->dds()->amend($request);
$response->isSuccess(); // true (status === 'SC_200_OK')
```

### Retracting a DDS

[](#retracting-a-dds)

Cancel a DDS in `SUBMITTED` status or withdraw one in `AVAILABLE` status.

```
use Eudr\Requests\V2\RetractDdsRequest;

$request = RetractDdsRequest::make()
    ->withDdsIdentifier('3f09ab3f-4c97-4663-8463-89d58f1d646b');

$response = $client->dds()->retract($request);
$response->isSuccess(); // true
```

### Retrieving a DDS

[](#retrieving-a-dds)

#### Single retrieval

[](#single-retrieval)

```
$response = $client->dds()->retrieve('3f09ab3f-4c97-4663-8463-89d58f1d646b');

$response->identifier;                     // UUID
$response->internalReferenceNumber;        // "MY-REF-001"
$response->referenceNumber;                // "24FRXVV3VOS991"
$response->verificationNumber;             // "SEKUYXPP"
$response->status;                         // DdsStatus::AVAILABLE
$response->rejectionReason;                // null or string
$response->communicationToOperatorDate;    // null or CA communication date
$response->communicationToOperatorMessage; // null or CA communication message
```

#### Batch retrieval (up to 100 UUIDs)

[](#batch-retrieval-up-to-100-uuids)

```
$responses = $client->dds()->retrieveMany([
    '3f09ab3f-4c97-4663-8463-89d58f1d646b',
    'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
]);

foreach ($responses as $response) {
    echo $response->identifier . ': ' . $response->status->value . "\n";
}
```

#### By internal reference number

[](#by-internal-reference-number)

```
// Returns the first match
$response = $client->dds()->retrieveByReference('MY-REF-001');

// Returns all matches (up to 1000)
$responses = $client->dds()->retrieveAllByReference('MY-REF-001');
```

### Cross-Supply-Chain Retrieval

[](#cross-supply-chain-retrieval)

#### Get statement by identifiers

[](#get-statement-by-identifiers)

Retrieve a supplier's DDS using their shared reference and verification numbers:

```
$response = $client->dds()->getStatementByIdentifiers(
    referenceNumber: '24FRIOBORU2228',
    verificationNumber: 'LWKAOH97',
);

$response->referenceNumber;      // "24FRIOBORU2228"
$response->activityType;         // "IMPORT"
$response->status;               // DdsStatus::AVAILABLE
$response->statusDate;           // "2024-09-23T11:05:00.000"
$response->operatorName;         // "FR DDS OPER TRAD AUTH REP"
$response->operatorCountry;      // "FR"
$response->associatedStatements; // [['referenceNumber' => '...']]
```

#### Get referenced DDS (V2 only)

[](#get-referenced-dds-v2-only)

Follow the chain of referenced DDS documents without requiring the original verification number:

```
$response = $client->dds()->getReferencedDds(
    referenceNumber: '25FR6CWUOLKN59',
    referenceDdsVerificationNumber: 'encrypted-verification-string',
);
```

### Echo Service

[](#echo-service)

Test connectivity and authentication. Available in acceptance/testing environments only.

```
$response = $client->echo('hello');
$response->result;      // Echo response from server
$response->isSuccess(); // true
```

---

Middleware
----------

[](#middleware)

The client supports a PSR-7 middleware pipeline for cross-cutting concerns.

### Retry

[](#retry)

Retries failed requests on 5xx responses and transport exceptions with exponential backoff:

```
use Eudr\Http\Middleware\RetryMiddleware;

$client = new EudrClient(
    config: $config,
    middleware: [
        new RetryMiddleware(maxAttempts: 3, baseDelayMs: 100),
    ],
);
```

### Logging

[](#logging)

Automatically enabled when a PSR-3 logger is provided in the config. Logs request method/URI and response status/duration.

```
$config = new Config(
    // ...
    logger: $monologLogger,
);
```

### Custom middleware

[](#custom-middleware)

Implement the `Middleware` interface:

```
use Eudr\Http\Middleware\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class RateLimitMiddleware implements Middleware
{
    public function process(RequestInterface $request, callable $next): ResponseInterface
    {
        $this->waitForRateLimit();
        $response = $next($request);
        $this->updateRateLimit($response);

        return $response;
    }
}
```

---

Error Handling
--------------

[](#error-handling)

All exceptions extend `Eudr\Exceptions\EudrException`:

```
RuntimeException
  └── EudrException
        ├── ConfigurationException   // Invalid config
        ├── ValidationException      // Request validation failed
        ├── XmlException             // Response XML parsing failed
        ├── ApiException             // SOAP fault from the API
        └── HttpException            // HTTP 4xx/5xx errors
              └── AuthenticationException  // HTTP 401/403

```

```
use Eudr\Exceptions\ApiException;
use Eudr\Exceptions\AuthenticationException;
use Eudr\Exceptions\HttpException;
use Eudr\Exceptions\ValidationException;
use Eudr\Exceptions\XmlException;

try {
    $response = $client->dds()->submit($request);
} catch (ValidationException $e) {
    // Missing required fields, invalid data formats
} catch (AuthenticationException $e) {
    // HTTP 401/403 - check credentials
    $e->statusCode;
    $e->responseBody;
} catch (ApiException $e) {
    // SOAP fault returned by the EUDR API
} catch (HttpException $e) {
    // Other HTTP errors (5xx, network issues)
    $e->statusCode;
    $e->responseBody;
} catch (XmlException $e) {
    // Response XML could not be parsed
}
```

### SOAP fault error details

[](#soap-fault-error-details)

The `ErrorResponse` object provides structured access to SOAP fault details from the TracesNT error namespace:

```
// ErrorResponse fields:
$error->faultCode;   // SOAP fault code
$error->faultString; // Human-readable error message
$error->detail;      // Raw XML detail string
$error->errors;      // ErrorDetail[] - parsed structured errors

// Each ErrorDetail contains:
foreach ($error->errors as $detail) {
    $detail->id;      // e.g. "EUDR-REFERENCE-NUMBER-INVALID"
    $detail->message;  // e.g. "Has not allowed characters"
    $detail->field;    // e.g. "Reference number"
}
```

---

API Versions
------------

[](#api-versions)

The package supports both V1 and V2 of the EUDR API. **V2 is the current recommended version.**

```
// V2 (default)
$client->dds()->submit($v2Request);
$client->dds()->amend($v2AmendRequest);
$client->dds()->retract($v2RetractRequest);
$client->dds()->retrieve('uuid');
$client->dds()->retrieveMany(['uuid-1', 'uuid-2']);
$client->dds()->retrieveByReference('MY-REF-001');
$client->dds()->retrieveAllByReference('MY-REF-001');
$client->dds()->getStatementByIdentifiers('REF-001', 'VER-001');
$client->dds()->getReferencedDds('REF-001', 'encrypted-verification');

// V1
$client->ddsV1()->submit($v1Request);
$client->ddsV1()->amend($v1AmendRequest);
$client->ddsV1()->retract($v1RetractRequest);
$client->ddsV1()->retrieve('uuid');

// Echo (connectivity test)
$client->echo('hello');
```

### Key differences between V1 and V2

[](#key-differences-between-v1-and-v2)

FeatureV1V2Operator address`nameAndAddress` (flat string)`operatorAddress` (structured fields)GoodsMeasureAll fields except `percentageEstimationOrDeviation`All fields including `percentageEstimationOrDeviation`Namespace prefix`v1`/`v11``v2`/`v21`Endpoint suffix`*ServiceV1``*ServiceV2`GetReferencedDdsNot availableAvailable### Enums

[](#enums)

**`OperatorType`** - `OPERATOR`, `TRADER`, `REPRESENTATIVE_OPERATOR`, `REPRESENTATIVE_TRADER`

**`ActivityType`** - `DOMESTIC`, `TRADE`, `IMPORT`, `EXPORT`

**`DdsStatus`** - `PENDING_CREATION`, `AVAILABLE`, `SUBMITTED`, `REJECTED`, `RETRACTED`, `CANCELLED`, `WITHDRAWN`, `ARCHIVED`, `UNKNOWN`

> Unknown API status values gracefully fall back to `DdsStatus::UNKNOWN`.

---

Development
-----------

[](#development)

```
composer install          # Install dependencies
composer test             # Run tests
composer analyse          # Static analysis (PHPStan level 9)
composer cs-check         # Code style check
composer cs-fix           # Fix code style
composer check            # Run all checks
```

Architecture
------------

[](#architecture)

```
src/
├── Config/           Configuration and credentials
├── Data/             Immutable value objects (Commodity, Producer, Address, etc.)
├── Enums/            OperatorType, ActivityType, DdsStatus
├── Exceptions/       Exception hierarchy
├── Http/             PSR-18 connector and middleware pipeline
│   └── Middleware/   Retry, logging, and custom middleware
├── Requests/         SOAP request builders
│   ├── Builders/     Fluent builders for Commodity, Producer, Operator
│   ├── V1/           V1-specific requests and XML traits
│   └── V2/           V2-specific requests and XML traits
├── Resources/        API resource classes (DDS operations)
├── Responses/        SOAP response parsers
│   ├── V1/           V1 response parsers
│   └── V2/           V2 response parsers
├── Support/          XML utilities, namespace constants, SOAP envelope parser
└── EudrClient.php    Main entry point

```

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance54

Moderate activity, may be stable

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/6aff1d0279e2e09f8c63b8b6279b436ee0c9e15d0e7a0ee1fddeba52e330afa7?d=identicon)[4bdullatif](/maintainers/4bdullatif)

---

Top Contributors

[![4bdullatif](https://avatars.githubusercontent.com/u/152618437?v=4)](https://github.com/4bdullatif "4bdullatif (1 commits)")

---

Tags

eudreudr-compliancephp

### Embed Badge

![Health badge](/badges/4bdullatif-eudr-php-client/health.svg)

```
[![Health](https://phpackages.com/badges/4bdullatif-eudr-php-client/health.svg)](https://phpackages.com/packages/4bdullatif-eudr-php-client)
```

###  Alternatives

[stripe/stripe-php

Stripe PHP Library

4.0k143.3M475](/packages/stripe-stripe-php)[twilio/sdk

A PHP wrapper for Twilio's API

1.6k92.9M270](/packages/twilio-sdk)[knplabs/github-api

GitHub API v3 client

2.2k15.8M186](/packages/knplabs-github-api)[facebook/php-business-sdk

PHP SDK for Facebook Business

90121.9M33](/packages/facebook-php-business-sdk)[microsoft/microsoft-graph

The Microsoft Graph SDK for PHP

65723.5M95](/packages/microsoft-microsoft-graph)[meilisearch/meilisearch-php

PHP wrapper for the Meilisearch API

73813.7M114](/packages/meilisearch-meilisearch-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
